目录

一、编译与链接

1.计算机语言

1)机器语言

2)汇编语言

3)高级语言

2.翻译方式

1)解释

2)编译

C语言的编译和链接过程:

二、进制

1)2进制

2)八进制

3)10进制

4)16进制

进制转换

1)二进制转十进制

2)二进制转八进制(方法可互转)

3)二进制转十六进制(方法可互转)

4)十进制转换二进制

5)八进制、十六进制转十进制

6)十进制转八进制、十六进制

7)十六进制转八进制(方法一样):

三、原码、反码、补码

1)源码

2)反码

3)补码

补码怎么推原码?

三、类型与变量

1)字符型

2)整型

3)浮点型

4)布尔型

sizeof()

signed和unsigned类型的值范围

1)char

2)short

3)int

强制类型转换:

整型提升

定义

如何整型提升+符号扩充

算术转换

!!!类型计算的练习题

1)同类型,比如char与char

2)不同类型,比如char和int

3)同类型,有无符号的计算

学校的老师很喜欢出一些奇葩的题,专升本也喜欢出这种题,比如:

变量

创建变量如下:

初始化:创建变量就给值

全局变量和局部变量

全局和局部名字相冲,优先局部

这些在内存存在哪里?

操作符

1)算术操作符: + 、- 、* 、/ 、%

我们讲点有区别的:

 1)/        ,类型截断

2)/        ,输出格式和类型不匹配

3)/        ,负数

4)%取模,不能为小数,可以为负数

2)移位操作符: << >>

左移<<

右移>>

1)逻辑右移:左边⽤0填充,右边丢弃

​编辑2)算术右移:左边⽤原该值的符号位填充,右边丢弃

不要写一些bug,比如移动负数:

3)位操作符: & | ^ ~

1.1)按位与&

二进制数字中,两个都为1才是1,不然的话为0

按位或|

二进制数字中,有一个是1的话就为1,两个为0才为0

按位异或^

二进制位上数值相同为0,不同为1

按位取反~

如果是0变为1,是1变为0,符号位也要取反

4)赋值操作符: = 、+= 、 -= 、 *= 、 /= 、%= 、<<= 、>>= 、&= 、|= 、^=

5)单⽬操作符: !、++、--、&、*、+、-、~ 、sizeof、(类型)

前置++和前置--

后置++和后置--

6)关系操作符: > 、>= 、< 、<= 、 == 、 !=

7)逻辑操作符: && 、||

8)条件操作符: ? :

9)逗号表达式: ,

10)下标引⽤[]、函数调⽤ ()、结构成员

操作符的优先级和结合性

优先级排行榜:

scanf和printf

printf

用法

占位符

多个占位符

常见的占位符

输出格式

最小宽度:允许占位符的最小宽度。

若希望左对齐,加个-号

显示正负号

只针对小数点位(加.)

scanf

用法

对scanf的一些理解

1)必须加

2)开头过滤空白字符

3)格式匹配

4)返回值

算法

定义:就是要计算机做的事情的代码步骤

特征:

表示:

1)流程图​编辑

2)N-S图

3)伪代码

4)计算机语言

算法+数据结构=程序

putchar和getchar

程序结构

1)顺序结构

2)分支(选择)结构

if语句

注意:在C语⾔中,0为假,⾮0表⽰真

其他情况else

if和else有多条语句

嵌套if

switch

上面现象叫case穿透,

default

循环结构

while

break和continue

for循环

while与for的区别:

do...while

循环嵌套

找出1-100的素数

数组

初始化

怎么操作数组

打印数组

数组在内存中

二维数组

定义:

初始化

操控二维数组

打印二维数组

二维数组的存储

字符类型与字符串

二维字符数组

打印三角形

字符串

定义:

注意事项:

1)输出不包括\0

2)printf用%s输出时,写的是str,而不是str[0],str[1]等等

3)碰到\0就结束,无论数组多长

4)可以用scanf对str赋值,但是不能直接对str修改

5)scanf输入多个str,用空格隔开

6)注意!scanf中,不能用&符号

字符串函数

gets和puts

strcat

strcpy

strcmp

strlen

strlwp和strupr

函数

库函数

自定义函数

定义与声明

形参和实参

return语句

1.返回类void时,可以提前return;结束,return后面什么都不接

2.返回类型与返回值类型不同,以返回类型为主

3.return结束就相当于函数结束

4.有if分支时,return要覆盖所有可能的情况

5.return后面可以是表达式,也可以是单纯的值

数组传参

嵌套调用和链式调用

static和extern

static修饰全局变量不被用,且extern采用外部变量

static修饰函数

指针

类比:

指针和内存

它是怎么拿取数据的?

指针在c语言中是怎么样的

指针变量

解引用操作*

还有一些比较有意思的题

指针变量的大小

指针基类型的意义

指针的计算

指针+-整数

指针-指针(不能相加)

传值和传址

传值

传址

数组传参的本质

&arr和arr和arr[0]有什么区别呢?

用来计算时,加加减减的单位会有所不同

二级指针

指针数组和数组指针

指针数组:

数组指针

结构体

声明如下:struct stu{};        注意要有分号

咋用呢?

第一种,声明之后,定义结构体变量并且初始化

第二种,声明的时候直接定义变量并且初始化

第三种,先声明结构体,然后顺带整个结构体变量,之后你再赋值

结构体数组

结构体指针

结构体传参

结构体传参传的是值,

结构体指针传参,会改变内容

结构体内存

共用体和枚举类型

共用体

结构体和共用体区别

枚举

文件操作

文件函数

putchar和getchar以及scanf和printf还有gets和puts区别


引言:

两万六字的广东专升本知识点,对于一些易错的我也专门拿出来提了下,希望能对大家有帮助。大家可以通过目录来查漏补缺。但是这些东西只能对于期末考试或者专升本,对于就业是完全不够的。

一、编译与链接

1.计算机语言

美国人说美国话,中国人说中国话,计算机说计算机话,就有了自己的语言。

1)机器语言

计算机只会说机器语言,它是由0、1的二进制代码组成的。

字母“A”在ASCII编码中对应二进制“01000001”

2)汇编语言

机器语言不大方便读,就出现了汇编语言,让助记符代替这些指令与数据

3)高级语言

C语言、Java、python等就是高级语言,这种更加易读易修改,

类似于人类语言,但是不能被计算机直接执行,执行速度也慢一些。

2.翻译方式

C语言不能直接被计算机读取,而是要通过翻译变成目标代码才行。翻译有两种方式:

1)解释

一边翻译一边执行,不产生目标代码,比如说JS这种实时性的。

2)编译

全部翻译为用机器语言表示的目标代码,用的很广泛,C语言就是这种。

C语言的编译和链接过程:

通过编译变成目标文件(计算机看得懂的),然后通过链接变成exe文件(能在内存运行的)

二、进制

1)2进制

最简单,也是计算机看得懂的。只有0和1组成。满2(下一位)进1,一个0或者1就是1bit

假设是一个字节(8个bit)存储5这个值的话,就是00000101,

因为101,如下图,也就是4+0+1=5

2)八进制

又0、1、2、3、4、5、6、7这八个数字组成,它不会和十进制或者16进制弄混,因为它会用开头为0提醒你,比如012,就是八进制的12

3)10进制

也是我们最熟悉的一个,但是十进制没有10,只有0123456789这十个数字。

4)16进制

由0123456789ABCDEF这16位组成,用的字符A代替10,后同。它和8进制一样,会有特殊字符提示,以0x开头,0xC指的就是十六进制的12

注意:这些都是通过ASCII值来让计算机识别的。DEC是十进制,而HEX是十六进制。如下图

如果想要更加详细的,看下图

注意:有一些特殊的符号我们直接输入的,我们会考虑转义字符,用' '圈起来

八进制:\ddd        

十六进制:\xhh

char A_char1 = '\x41';  // 十六进制41是十进制的65,是字符'A'
char A_char2 = '\101'; // 八进制101是十进制的65,是字符'A'

进制转换

我们的8、10、16进制都需要转化为2进制供计算机识别,那么怎么转换呢,考试时,各种转换怎么转?

1)二进制转十进制

把每一位都用2^n表示,从右往前算

2)二进制转八进制(方法可互转)

因为3位二进制位就能表示所有八进制,比如八进制最多就是7,我直接用111就能表示了。

我们只需要把上面的图记住,就能直接配凑了,采用3-3-2的形式,如下:

二进制011 010 11的八进制为0153,前面 要加个0区分十进制 

3)二进制转十六进制(方法可互转)

同样的,我们16位最高为F,只需要4位二进制位就能配凑出来了

开始配凑,采用4-4,假设是0002 1110,则十六进制为0x2E(加0x区分十进制):

4)十进制转换二进制

5)八进制、十六进制转十进制

6)十进制转八进制、十六进制

十六进制也如下,除16取模就好了

7)十六进制转八进制(方法一样):

先转成二进制,再转成八进制(十六进制)

三、原码、反码、补码

正数的原码反码补码,都一样的。负数的如下:

1)源码

比如说在一个字节中,

我们的数字-5(说的都是人类的十进制)就是二进制的10000101(第一位为符号位),

这个数就是原码,原原本本的没有加任何添加剂的.

2)反码

在原码的基础上,按位取反(符号位不变),反码为11111010

3)补码

反码+1,补码为11111011

为啥用补码计算?

计算机中只能进行加法计算,如果用原码计算的话:负数的计算1+(-1)就会出现问题,得出的答案为2而不是0

补码怎么推原码?

原码:补码取反+1

三、类型与变量

内置类型:就是我能直接用的,

自定义类型:就是我需要根据语法自己构建出来,

类型如下:

而类型分为有符号(signed)和无符号(unsigned),signed包括±,unsigned不包括±。

1)字符型

char (=signed char)
unsigned char

一个字符类型1个字节,用''单引号,如:char ch='A';

2)整型

//短整型
short(=signed short)
unsigned short

//整型
int(=signed int)
unsigned int

//长整型
long(=signed long)
unsigned long

//更长的整型 c99引入
long long(=signed long long)
unsigned long long

在VS2022 x64上,short为2字节,int为4字节,long为4字节,long long为8字节。int n=1;

为啥long为4字节

每个编译器的厂商制定的标准不一样,可能A厂就为4,B厂就为8

在C语言语法中,long>=int的长度,在考试中,有争议性的题目是基本上不会出的。

3)浮点型

浮点就是小数,3.1415这种的

//单精度浮点型
float
//双精度浮点型
double
//更高精度的双精度浮点型
long double

float为4字节,double为8字节,long double为8字节,

语法中long double>=double,不同厂商规定的也不同。

float f=3.14,double d=3.3123

4)布尔型

bool

bool flag = true;
if (flag)
	printf("hello");

bool为1字节,值有false和true。

sizeof()

检测类型的长度,从上到下答案为4448,默认小数为double类型

int main() 
{
	int a = 10;
	printf("%zd\n", sizeof(a));
	printf("%zd\n", sizeof a);//a是变量的名字,可以省略掉sizeof后边的()
	printf("%zd\n", sizeof(int));
	printf("%zd\n", sizeof(3 + 3.5));
	return 0;
}

signed和unsigned类型的值范围

因为0也包括在正整数里,所以正数部分会-1

1)char

char占1个字节,也就是8个比特位,一个比特位有2个数(0和1)可以选择。

signed范围-2^7 ~ 2^7-1    ->    -128~127 

unsigned范围0 ~ 2^8-1    ->    0~255

2)short

short占2个字节,16个比特位

signed-2^15 ~ 2^15-1    ->    -32,768~32,767

unsigned0 ~ 2^16-1  ->  0~65,535

3)int

int占4个字节,32个比特位

signed-2^31 ~ 2^31-1    ->    -2,147,483,648~2,147,483,647

unsigned0 ~ 2^32-1  ->  0~4,294,967,295 

其他的类推就行了。

强制类型转换:

我想打游戏,对象一定要让我逛街。强扭的瓜不甜就是强制类型转换。

int main()
{
	//意思是将3.14强制类型转换为int类型,这种强制类型转换只取整数部分
	int a = (int)3.14;
	return 0;
}

这玩意儿容易丢数据,慎用。

类型之间计算(CPU底层)

整型提升

定义

因为char和short都是一家人,都是整型家族的。

所以要整型提升,也就是没有到int的字节长度的,在CPU的时候都变成int字节。

如何整型提升+符号扩充

//负数的整形提升
char c1 = -1;
变量c1的⼆进制位(补码)中只有8个⽐特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,⾼位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的⼆进制位(补码)中只有8个⽐特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,⾼位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//⽆符号整形提升,⾼位补0

如上面的代码,最后的结果还得再截断成11111111和00000001这两个就是c1和c2的结果。

如果要%d打印的话,还得继续放在内存上再一次整型提升。

算术转换

简单说,就是类型小的±类型大的,的时候怕出事,要都变成类型大的。

(char和short都变成int了)如下图,从下到上,进行算术转换。

long double
double
float
unsigned long int
long int
unsigned int
int

!!!类型计算的练习题

1)同类型,比如char与char

CPU里面是补码:char有1个字节,

ch1为-1的话,就是11111111,整型提升11111111111111111111111111111111,

ch2为1的话,就是00000001,整型提升0000000000000000000000000001,

相加的话0000000000000000000000000000(第一位进一丢失数据)

把他装进char ch3,计算结果截断变成char长度:00000000(补码)->00000000(原码),

原码为0装进去ch3。

2)不同类型,比如char和int

char ch=1,int n=1。

那么ch为00000001,整型提升0000000000000000000000000001。

n为0000000000000000000000000001(不需要整型提升),

不用算术转换char(已经整型提升为int,不需要转了,如果是double和int就需要算术转换成double,为了不丢数据),

然后相加变成0000000000000000000000000010,也就是2。存char就截断,存int就不变。

3)同类型,有无符号的计算

unsigned int n1=1,signed int n2=-1,都是int或以上不用整型提升,

然后开始算术转换成unsigned int因为他的等级更高。

n1就变成00000000000000000000000000000001。

n2最开始补码是111111111111111111111111111111111111(signed),

如果变成unsigned的话就是4,294,967,295,符号位也算上值,

相加为00000000000000000000000000000000(补码),原码就为0,所以n3=n1+n2=0。

学校的老师很喜欢出一些奇葩的题,专升本也喜欢出这种题,比如:

int a=2;
printf("%d",a+'E'-'A');

1)整型提升:'E'和'A'就变成了69和65(int)

2)计算:2+69-65=6

变量

计算机为了描述会经常改变的量,就创建了变量。

比如说,我们今年20岁,明年就多一岁了。还有温度,今天40度,明天广东下个大暴雨就30度了。这都都可以称为变量。

创建变量如下:

int age; //整型变量
char ch; //字符变量
double weight; //浮点型变量

初始化:创建变量就给值

int age=20;
char ch='A';

全局变量和局部变量

int b = 3;//全局变量
int main()
{
	int a = 2;//局部变量
	return 0;
}
全局和局部名字相冲,优先局部
int b = 3;
int main()
{
	int b = 2;
	printf("b的值为:%d", b);//输出2
	return 0;
}

这些在内存存在哪里?

操作符

1)算术操作符: + 、- 、* 、/ 、%

+-*/上过小学的都知道,我们直接看代码:

int main()
{
	int a = 2;
	int b = 2;
	printf("%d", a + b);//4
	printf("%d", a - b);//0
	printf("%d", a * b);//4
	printf("%d", a / b);//1
}

我们只需要知道,计算机就是为了算数学的计算,所以跟数学差不多。

我们讲点有区别的:

 1)/        ,类型截断

int main()
{
	int a = 13;
	int b = 2;
	double n = a / b;
	printf("%d\n", a/b);//6
	printf("%f\n", n);//6.000000
	return 0;
}

为啥不是6.5,有人说这不是用%d输出嘛,所以就是整数。

那后面我用double存起来,不也是6.000000嘛。为啥呢?

因为a/b先计算,两个int整数计算就为int答案,多出来的小数就截断。就是是先存到double的n也是会被截断的。如果要输出6.5,先变成double类型,这样就会算术转换,

如下:

double n = (double)a / b;

2)/        ,输出格式和类型不匹配

int main()
{
	int a = 13;
	int b = 2;
	printf("%f", a / b);//0.000000
	return 0;
}

大家就在想为什么不是6.000000,而是0.000000这个是大家都想不到的。

因为计算机是没有脑子的,它只是一个机器,执行你的指令。

你给的指令就是a/b压入栈为6(int类型只能得int),然后进行%f打印压入栈。

你要打印的时候是8个字节,然后你的值只有4字节,计算机就开始凑字数,往后面拿4个字节给你。

你也不知道这四个字节是啥数据。凑8个数据给你,输出0.000000是你运气好,差点的就是烫烫烫烫烫烫烫烫了。

3)/        ,负数

int main()
{
	int n1 = -12;
	int n2 = -2;
	int n3 = 12;
	int n4 = 2;
	printf("%d\n", n1 / n2);//6
	printf("%d\n", n1 / n4);//-6
	printf("%d\n", n3 / n2);//-6
	printf("%d\n", n3 / n4);//6
}

4)%取模,不能为小数,可以为负数

int main()
{
	int n1 = -13;
	int n2 = -2;
	int n3 = 13;
	int n4 = 2;
	printf("%d\n", n1 % n2);//-1
	printf("%d\n", n1 % n4);//-1
	printf("%d\n", n3 % n2);//1
	printf("%d\n", n3 % n4);//1
}

注意:负数取模时,只需要看被模数是不是负数,如果是就变成负数,不是就整数。


2)移位操作符: << >>

左移<<

右移>>

有两种方法,不同编译器不一样,更多的是算术右移

1)逻辑右移:左边⽤0填充,右边丢弃

2)算术右移:左边⽤原该值的符号位填充,右边丢弃

不要写一些bug,比如移动负数:
int num = 10;
num>>-1;//error

3)位操作符: & | ^ ~

1.1)按位与&

二进制数字中,两个都为1才是1,不然的话为0
int main()
{
	char ch1 = -1;
	//原码:10000001
	//反码:11111110
	//补码:11111111
	char ch2 = 2;
	//原码:00000010
	//反码:00000010
	//补码:00000010
	char ch3 = ch1 & ch2;
	//ch1:11111111
	//ch2:00000010
	//ch1 & ch2(补码):00000010
	//原码:00000010
	printf("%d", ch3);//2
	return 0;
}

按位或|

二进制数字中,有一个是1的话就为1,两个为0才为0
int main()
{
	char ch1 = -1;
	//原码:10000001
	//反码:11111110
	//补码:11111111
	char ch2 = 2;
	//原码:00000010
	//反码:00000010
	//补码:00000010
	char ch3 = ch1 | ch2;
	//ch1:11111111
	//ch2:00000010
	//ch1 | ch2(补码):11111111
	//原码:10000001
	printf("%d", ch3);//-1
	return 0;
}

按位异或^

二进制位上数值相同为0,不同为1
int main()
{
	char ch1 = -1;
	//原码:10000001
	//反码:11111110
	//补码:11111111
	char ch2 = 2;
	//原码:00000010
	//反码:00000010
	//补码:00000010
	char ch3 = ch1 ^ ch2;
	//ch1:11111111
	//ch2:00000010
	//ch1 ^ ch2(补码):11111101
	//原码:10000011
	printf("%d", ch3);//-3
	return 0;
}

按位取反~

如果是0变为1,是1变为0,符号位也要取反
int main()
{
	char ch2 = 2;
	//原码:00000010
	//反码:00000010
	//补码:00000010
	char ch3 =~ ch2;
	//ch1:11111111
	//ch2:00000010
	//~ch2(补码):11111101
	//原码:10000011
	printf("%d", ch3);//-3
	return 0;
}

4)赋值操作符: = 、+= 、 -= 、 *= 、 /= 、%= 、<<= 、>>= 、&= 、|= 、^=

剩下的都差不多,自己改一下符号就行了。   

int main()
{
	int a = 1;//1
	a = 2;//2	
	a += 2;//4	->a=a+2
	a -= 1;//3	->a=a-1
    int b=0;
    a=b=3//连续赋值,c语言是支持的(3赋值给b,b赋值给a)
	return 0;
}

5)单⽬操作符: !、++、--、&、*、+、-、~ 、sizeof、(类型)

+正值,-负值,&取地址,*指针,sizeof(可以看前面的内容)

!非

int main()
{
	if (!0) {
		printf("hello");
	}
	return 0;
}

前置++和前置--

int a=1;
//先++,再赋值
int b=++a;//a=2后b=2
//先--,再赋值
b=++a;//a=1后b=1

后置++和后置--

int a=1;
//先赋值,再++
int b=a++;//b=1后a=2
//先赋值,再--
b=a--;//b=2后a=1

6)关系操作符: > 、>= 、< 、<= 、 == 、 !=

比较两个数字的关系:>=和<=(≥和≤),==就是(3=3),(3!=4)和数学是差不多的


7)逻辑操作符: && 、||

两个一起是&&,只需要一个就行||

int main()
{
	if (3==4||3==2) {
		printf("hello");//不打印
	}
	return 0;
}

8)条件操作符: ? :

三目表达式,如果?前面成立,输出第二个表达式,不成立输出第三个表达式

int main()
{
	int a = 9;
	if (a > 0 ? a : 0) {
		printf("a大于0");
	}
	return 0;
}

9)逗号表达式: ,

从左往右依次执行,但是只会结果为最后一个逗号后面的表达式

int main()
{
	int a, b;
	printf("%d", (a = 1, b = 2, a + b));
	return 0;
}

10)下标引⽤[]、函数调⽤ ()、结构成员

下标索引:操作数组

int main()
{
	int arr[3] = { 1,2,3 };
	arr[0] = 2;
	printf("%d",arr[0]);
	return 0;
}

函数调用,比如int main()

结构成员:后面讲

操作符的优先级和结合性

优先级:根据级别,看看谁先算,下面就是4 *5先算,优先级高,跟数学差不多吧

int n = 3 + 4 * 5;

结合性:级别相同时,从左到右算,先5*6再/2

int n = 5 * 6 / 2;

优先级排行榜:

scanf和printf

都是格式化的输入输出

这两种属于别人写好的,我们可以直接用的函数,使用的话得说明是哪里来的,需要在代码前这样写,standard input output(标准输入输出流)

#include <stdio.h>

printf

用法

#include <stdio.h>
int main()
{
	printf("hello world");
	return 0;
}

如上图,光标会停在d后面这里。如果你想要停在下一行就可以加\n,或者换行都能用\n

占位符

下面输出的内容都是一样的:我今年10岁。

int main()
{
	//printf("hello world");
	printf("我今年10岁\n");
	printf("我今年%d岁\n",10);
	int a = 10;
	printf("我今年%d岁\n", a);
	return 0;
}

相当于你在排队,去丢了个垃圾,让你兄弟帮你占位,你回来还是那个位置,就你兄弟就是占位符。

多个占位符

printf("%s今年%d岁了", "zhangsan", 12);

常见的占位符

常见的如上,日常使用基本够了,不够的话再上官网查就行了。

输出格式

最小宽度:允许占位符的最小宽度。

上面的其实123的前面有2个空,加123就是5个,默认右对齐。

若希望左对齐,加个-号

保留几位小数

小数后面默认显示6位,小数点算一位,所以前面还有2个空格

显示正负号

printf("%+d\n", 12); // 输出 +12
printf("%+d\n", -12); // 输出 -12

只针对小数点位(加.)

scanf

用法

占位符+&取地址(scanf传递的不是值,而是地址,将变量 age 的地址指向⽤⼾输⼊的值

int main()
{
	int age = 0;
	scanf("%d", &age);
	printf("age为%d", age);
	return 0;
}

对scanf的一些理解

1)必须加

#include <stdio.h>

2)开头过滤空白字符

scanf("%d%d%f%f", &i, &j, &x, &y);

不管开头是空格、tab键、换行等等,都不影响数据

如果是已经输入一个数据之后,你必须用“空白字符”来隔开不同的数据

注意:除了 %c 以外,都会⾃动忽略起⾸的空⽩字符。空白字符也是字符。

3)格式匹配

错误示范

你scanf要什么格式,你就得用什么格式

4)返回值

就是成功输入的多少个数据,用 int 接收

如果一个都不输入,将为-1

算法

定义:就是要计算机做的事情的代码步骤

特征:

1)输入:在算法中可以又零个或者多个输入

2)输出:在算法中至至少有一个输出

3)有穷性:任意一个算法在执行又穷哥计算步骤后必须终止

4)确定性:算法的每一个步骤都具有确定的含义,不会出现二义性

5)可行性:算法的每一步都必须是可行的

表示:

1)流程图

2)N-S图

3)伪代码

4)计算机语言

就是我们现在写的C语言的代码等等

算法+数据结构=程序

这么理解:数据结构提供底层基础,算法就是建设,也就是上层结构

 IO流,输入输出

putchar和getchar

用于字符的输入输出

int main()
{
	//getchar从键盘输入一个char送给a
	char a = getchar();
	//将char a输出
	putchar(a);
	return 0;
}

当你敲下字符时,并非敲一个字符就直接送给计算机,

而是先存进缓存区,等着按下enter的时候,才送到内存中供计算机享用,这对于scanf也适用。

程序结构

1)顺序结构

没啥好讲的,也是最简单的。一行一行执行就是顺序结构,比如说写100行printf就是顺序结构

#include <stdio.h>
int main()
{
	printf("hello world");
	return 0;
}

2)分支(选择)结构

if语句

真就执行,假就不执行。

	if (12 % 2 == 0) 
		printf("我可以被2整模");
注意:在C语⾔中,0为假,⾮0表⽰真

在N-S图如下:

其他情况else
	if (12 % 2 == 0)
		printf("我可以被2整模");
	else
		printf("我不能被2整模");

else只能与最近的if进行匹配

if和else有多条语句

这时候需要用大括号阔起来,美观又漂亮

	int age = 16;
	if (age >= 18) {
		printf("成年了");
		printf("可以考驾照");
	}
	else {
		printf("没有成年");
		printf("不能考驾照");
	}

如果不用括号括起来呢?(它判定可以考驾照和下一条if同级别)

如果是else将会报错,因为没有if跟他关联,它已经断开了。

嵌套if

if里面还有if,像俄罗斯套娃

int main()
{
	if (1 != 2) {
		if (2 != 3) {
			//...
		}
	}
	return 0;
}

switch

也是分支的一种写法,对于某些场合是if的升级版本。

里面switch()里面只能是数值和字符,绝对不能是小数。case也是如此。

int main()
{
	int day;
	scanf("%d", &day);
	switch (day) 
	{
	case 1:
		printf("周一");
		break;
	case 2:
		printf("周二");
		break;
	case 3:
		printf("周三");
		break;
	case 4:
		printf("周四");
		break;
	case 5:
		printf("周五");
		break;
	case 6:
		printf("周六");
		break;
	case 7:
		printf("周日");
		break;
	}
	return 0;
}

注意事项:记得写break,不然的话:

上面现象叫case穿透,

没有break它就会继续执行。

也可以应用在一些多个都需要统一输出的时候:

default

如果输入不符合规定的输入,比如18,没有周18,这个是错误的。我们可以default来规范。

case和default颠倒顺序也是可以的,没有硬性要求。

但是为了美观还是建议把case放前面,default放后面。

循环结构

while

最简单的一种循环,while里面为真就循环,为假就退出循环

int main()
{
	int i = 1;
	while (i < 10)
	{
		printf("%d ", i);//1 2 3 4 5 6 7 8 9
		i++;
	}
	return 0;
}
break和continue

break终止循环

int main()
{
	int i = 1;
	while (i < 10)
	{
		if (i == 2) {
			break;
		}
		printf("%d ", i);//1 
		i++;
	}
	return 0;
}

continue跳出这次循环,开始下次循环

int main()
{
	int i = 1;
	while (i < 10)
	{
		if (i == 2) {
			continue;
		}
		printf("%d ", i);//1 死循环
		i++;
	}
	return 0;
}

for循环

while循环的pro版本,使用更方便,频率高

for里面有两个表达式,第一个是初始条件,第二个是要遵守的条件,第三个是变化的条件

int main()
{
	for (int i = 1; i < 10; i++) {
		printf("%d ", i);//1 2 3 4 5 6 7 8 9
	}
	return 0;
}

注意哦:i=1的时候要先执行一次,执行完再回来for的时候,执行i++,看看i<10不,为真才能进来。

同样的,break和continue也适用,跟while是差不多的

int main()
{
	for (int i = 1; i < 10; i++) {
		if (i == 4) {
			continue;
		}
		printf("%d ", i);//1 2 3 5 6 7 8 9(少个4)
	}
	return 0;
}

为啥跟while那个答案不一样?

因为这个先i++,while那个是i==4之后继续循环,还等于4,一直执行不到i++。

while与for的区别:

do...while

先执行一次的while,先do一次再判断(没有开车~~)

举例场景:假设我需要判断我输入的数是几位数,比如1234是4位,2是一位。code如下:

int main()
{
	int a,count=0;
	scanf("%d", &a);
	do
	{
		count++;
		a /= 10;
	} while (a);
	printf("%d", count);
	return 0;
}

为啥要用do...while,因为while的话假设我输入0,就跳出循环了。

如果是dowhile的话,就算是0也会先do一次count++一次,为1。

continue和break也适用,具体的大家可以根据实际场景来书写。

循环嵌套

找出1-100的素数

素数:大于1,只能被自己和1整除,其他都除不了,比如7

int main()
{
	for (int i = 2; i <= 100; i++) 
	{
		int flag = 1;
		for (int j = 2; j < i; j++) 
		{
			if (i % j == 0)
			{
				flag = 0;
				break;
			}
		}
		if (flag == 1)
		{
			// 2 3 5 7 11 13 17 19 23 29 31 37 41 
			// 43 47 53 59 61 67 71 73 79 83 89 97
			printf("%d ", i);
		}
	}
}

为什么是模,而不是除。因为除的话,2/3的话就是0了

数组

我们需要一个变量就是int a,char ch等等。如果我需要十个,那么我要定义a1,a2....a10等等太麻烦了,这时候数组就出现了。

数组就是把相同类型的变量都放在一起,用法如下:

	int arr[10];
	char chs[10];
	double d[10];

定义的长度必须是整型,不能是3.14这些,也不能是变量

虽然c99中可以用变量,但是很多编译器都不允许用,并且如果考试考到的话,也是选不可以用变量的

初始化

在定义的时候就给值,就叫初始化

	//完全初始化
	int arr1[5] = { 1,2,3,4,5 };
    int arr[]={1,2,3,4,5};//直接给定值,可以不用长度
	//不完全初始化
	int arr2[5] = { 1 };

当第二种初始化时,只有一个元素有值,其他自动都填充为0。

如果不初始化,里面的值是随机的,也就是垃圾值(可能上个程序留下的等等,但如果你打断点的话一般都是-858993460,int数组的话)

怎么操作数组

我们使用的是下标,用下标来找到各个数据的位置

为什么下标从0开始?

因为指的是内存中的偏移量,第一个数据就是arr的地址,所以偏移量为0

如下:

	int arr[5] = { 1,2,3,4,5 };
	printf("%d", arr[1]);//打印2

打印数组

int arr[10] = { 1,2,3,4,5,6,7,8,9,10};
int i;
for (i = 0; i < 10; i++)
{
	printf("%d ", arr[i]);
}

输入与打印

	int arr[10];
	int i;
	for (i = 0; i < 10; i++)
	{
		scanf("%d", &arr[i]);
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}

数组在内存中

可以看到,在内存中的地址是连续的,也就是数组其实是申请了一块连续的空间。

int main()
{
    int arr[10] = {0};
    printf("%d\n", sizeof(arr));//40
    return 0;
}

输出40,以字节的单位。也就是说arr的类型大小:类型*个数

下面就是计算数组个数的方法

int main()
{
    int arr[10] = {0};
    int sz = sizeof(arr)/sizeof(arr[0]);
    printf("%d\n", sz);//10
    return 0;
}

二维数组

类似于矩阵,我们的买的商品房,一楼二楼,101,102,201,202这样的,如下图。

定义:

下面就是2行3列的结构

	int arr[2][3];
	char chs[2][3];

初始化

	int arr1[2][3] = {1,2,3,4,5,6};
	int arr2[][3] = {1,2,3,4,5,6};//可以不给行,根据列就能算出行
	int arr3[2][3] = { {1},{2,4,5} };//给特定行赋特定值

能省略行,不能省略列,因为可以根据列来推出行,有4个值,分2列,那只能是2行。

如果初始化一个值,剩下没有初始化的都是0,如果一个值都没初始化,那就是随机值,可能烫烫烫

操控二维数组

int main()
{
	int arr[3][5] = { 1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7 };
	printf("%d\n", arr[2][4]);//7
	return 0;
}

打印二维数组

int main()
{
	int arr[3][5] = { 1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7 };
	int i, j;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

二维数组的存储

虽然我们看的是分行的,但是实际上,他们是连续的,就算是二维数组也是连续的。因为都是int类型的四字节,间隔都是四字节。

字符类型与字符串

其实就是一维数组里面放字符

int main()
{
	char chs[5] = { 'h','e','l','l','o' };
	int i;
	for (i = 0; i < 5; i++)
	{
		printf("%c", chs[i]);//hello
	}
	return 0;
}

二维字符数组

打印三角形
int main()
{
	int i, j;
	char chs[2][3] = { {' ','*'},{'*',' ','*'} };
	for (i = 0; i < 2; i++)
	{
		for (j = 0; j < 3; j++)
		{
			printf("%c", chs[i][j]);
		}
		printf("\n");
	}
	return 0;
}

运行如下:

字符串

在c语言中其实没有专门的字符串的类型,像Java、python那些就有。

在c语言中我们用的是字符数组,char chs[]这样的

定义:

int main()
{
	char str1[10] = { "hello!" };
	char str2[10] = "hello!";
	char str3[] = "hello!";//常见,书写方便
	return 0;
}

注意:在字符串中最后一个字符是\0,表示我就在这截断了,这个字符啥也不表示。

如果是helloworld呢,已经是10个了,没有\0的位置呢?

int main()
{
	char str[10] = { "helloworld" };
	printf("%s", str);//运气好正常输出
	printf("%d", strlen(str));//异常输出:烫烫烫烫
	return 0;
}

为什么str能正常输出?因为运气好,在别处可能就不行了。

为什么strlen是乱码?因为strlen是看\0之前的,它找不到\0就一直找,就会找到别人那里就会报错。

我们知道这个之后,也可以根据自己需要放\0在字符数组:

注意事项:

1)输出不包括\0
2)printf用%s输出时,写的是str,而不是str[0],str[1]等等
3)碰到\0就结束,无论数组多长
4)可以用scanf对str赋值,但是不能直接对str修改
char a[10];
a = "aaa"; // 错误!
char a[10];
scanf("%s", a); // 正确!

因为a是自定义的类型,数组名代表的是地址,地址就算0x100这种的,是常量不能修改的。但是如果scanf的话,就是对a这个地址里面的东西进行修改,这个是可行的。

5)scanf输入多个str,用空格隔开

如果是只输入给一个str,那么就是how\0\0\0\0\0。。。(输出完剩下所有空位的\0)

6)注意!scanf中,不能用&符号
scanf("%s",str);

因为这种自定义类型本来名字就表示地址,str表示0x100这样的,里面的值才是,如图:

字符串函数

gets和puts
int main()
{
	char str[10];
	gets(str);//输入hello
	puts(str);//输出hello
	return 0;
}

不仅可以用scanf,还可以用gets

strcat
int main()
{
	char str1[11] = "hello";
	char str2[11] = "world";
	strcat(str1, str2);
	printf("str1:%s,str2:%s", str1, str2);
	//str1:helloworld,str2:world 也就是追加到str1
	return 0;
}

注意str1得放得下,不然会栈溢出

strcpy
int main()
{
	char str1[11] = "hello";
	char str2[11] = "world";
	strcpy(str1, str2);
	printf("str1:%s,str2:%s\n", str1, str2);
	//str1:world,str2:world 也就是把后一个覆盖到前一个
	char str3[11] = "cxxxxxxx";
	char str4[11] = "111";
	strcpy(str3, str4);
	printf("str3:%s,str4:%s", str3, str4);
	//str3:111,str4:111 因为字符串\0结尾,直接给带过去,输出111\0结束
	return 0;
}
strcmp
int main()
{
	char str1[5] = "abc";
	char str2[5] = "abb";
	char str3[5] = "abc";
	int num1 = strcmp(str1, str2);
	int num2 = strcmp(str1, str3);
	printf("%d,%d", num1,num2);//1,0 
	//主要看ascii值,第一个值比较然后第二个值比较,直到不相等,或者结束
	//1表示大于,0表示等于,-1表示小于
	return 0;
}
strlen
int main()
{
	int n=strlen("你好");//单位为字节,你好表示4个字节
	int n2 = strlen("hello");
	printf("%d\n", n);
	printf("%d", n2);//\0前面有几位数,结果为5
	return 0;
}
strlwp和strupr
int main()
{
	char str[20] = { "asdADasdDFFAsd" };
	strlwr(str);
	printf("%s\n", str);//asdadasddffasd
	strupr(str);
	printf("%s", str);//ASDADASDDFFASD
	return 0;
}

函数

函数就是为了实现某一类功能的抽离出来的一段代码

比如我想要相加a+b,但是我每次相加都要自己写a+b,

这时候用add方法,要相加的时候,直接调用add方法就能帮我们自动相加了。

库函数

库函数:就是厂商给我们做好的,我们可以直接用的

vs就是微软的厂商,每个厂商设计的可能不一样,但是都是大差不差的。

其实我们很早就用过库函数了,比如printf和scanf

库函数
#include<stdio.h>
#include<math.h>
int main()
  {
	printf("你好呀\n");
	float f=sqrt(64);
	printf("%f", f);
	return 0;
}

用法很简单:引入头文件,就能直接用了

比如printf函数是在stdio.h这个头文件,开根号sqrt是在math.h这个头文件。

我们直接#include<>就能直接导入了。就能直接用了。

自定义函数

库函数没什么含金量,都是别人做的,考试考的多的都是自己做的,也就是自定义函数。

我们根据自己的需求,自己做一个函数。比如我想要两个数相加,2+3这样的。

代码如下:

int add(int a, int b) 
 {
	return a + b;
}

int main() 
 {
	int sum1 = add(2, 3);
	int x = 1, y = 2;
	int sum2 = add(x, y);
	printf("sum1=%d,sum2=%d\n", sum1, sum2);
	return 0;
}

我们只需要在main函数上方写自定义函数就行了,如上面的,add是个名字,你下次调用就是用这个名字。int是返回类型,int a和int b是形式参数。{}里面的都是函数体

意思就是说,我需要两个数,一个是a,一个是b。

你需要给我这两个数,然后我会给你加工,计算出来,用int类型的值return给你。

你要做的就是在main中用这个函数,也就是:名字(参数)。

add(2,3)。其中也可以用变量去传参.

我们再来一个,打印菜单

我好像不需要返回值,直接printf好像就行了。

void print() 
 {
	printf("------菜单------\n");
	printf("------程序------\n");
	printf("------选择------\n");
	printf("------设置------\n");
}
int main() 
 {
	print();
	return 0;
}

如果我不需要返回值,就直接用void就行了。然后直接在main中调用就好了

定义与声明

如果一个函数在main函数下面,就需要声明,不然会报错,因为代码是从上往下看的,计算机都不知道你有没有这个函数,它就直接说不知道这个函数

如下:

//函数的声明
int add(int a, int b);
void print();
int main() 
 {
	//声明放在这也是可以的
    //...
	int sum1 = add(2, 3);
	int x = 1, y = 2;
	int sum2 = add(x, y);
	printf("sum1=%d,sum2=%d\n", sum1, sum2);
	print();
	return 0;
}
int add(int a, int b) 
 {
	return a + b;
}
void print() 
 {
	printf("------菜单------\n");
	printf("------程序------\n");
	printf("------选择------\n");
	printf("------设置------\n");
}

就相当于我有个函数是有的,我先告诉你有这个东西。

形参和实参

函数int add(int a,int b)中,a和b就是形参,形式参数。

在main中,直接调用add方法,如add(2,3);中的2和3就是实参,实际参数。

形参自己是没有内存空间的,问就是省内存。

它只有在被调用的时候才会开辟一个空间给形参。否则是没有空间的,然后函数调用完之后就关掉。

实参在main函数中,他是有自己的内存空间的。

形参是实参的⼀份临时拷⻉

return语句

1.返回类void时,可以提前return;结束,return后面什么都不接
2.返回类型与返回值类型不同,以返回类型为主
3.return结束就相当于函数结束
4.有if分支时,return要覆盖所有可能的情况
5.return后面可以是表达式,也可以是单纯的值

数组传参

数组传参传的不是值,而是地址

arr->首元素地址

&arr[0]->首元素地址

&arr->整一个数组的地址

void set_arr(int arr[10], int sz) 
{
	for (int i = 0; i < sz; i++)
	{
		arr[i] = -1;
	}
}
//传参也可以不写arr的个数
void print_arr(int arr[], int sz) 
{
	for (int i = 0; i<sz; i++) 
	{
		printf("%d ", arr[i]);
	}
}
int main() 
{
	//void set_arr(int arr[], int sz);
	//void print_arr(int arr[], int sz);
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	set_arr(arr, sz);
	print_arr(arr, sz);
	return 0;
}

传参arr,相当于把arr的首元素的地址传过去,

但是形参不开辟内存,直接通过地址,对本身进行修改。

形参int arr[]在程序中会被解析成int *arr这个指针变量,存放的就是首元素的地址。

不是说数组传参不开辟内存嘛,int*arr不就是一块空间嘛?

是一块空间,注意的是,这里说的空间是对原数组的,不需要重新开辟一块数组存。

嵌套调用和链式调用

嵌套调用:

指的是一个函数的调用会调用其他的函数。

#include<stdio.h>
int main()
  {
	printf("你好呀\n");
	return 0;
}

上面就是main函数嵌套printf函数,main函数的调用是系统调用的,其他都没资格。

链式调用

把一个函数的结果给另一个函数做形参

#include <stdio.h>
int main()
{
    printf("%d\n", strlen("abcdef"));//链式访问
    return 0;
}

static和extern

static修饰局部变量,效果可以累加,也就是保留值。

static修饰全局变量不被用,且extern采用外部变量

代码2编译失败,代码1编译成功,如果不想全局变量被用,用static修饰

static修饰函数

和全局变量一样,就是不被用。1编译成功,2失败。

指针

这个是c语言中最难理解的一部分,给我一些时间,我会让你对指针有不一样的认识。

类比:

你去别的学校找你同学,但是这个学校很大,你不知道去哪找。

你就问他,他说在a栋605。也就是说它在这个宿舍,你去这里就能找到他。

指针和内存

在计算机中,一个程序的运行离不开内存,它是在内存中运行起来的,它有很多数据需要拿取,计算机需要知道每个数据放哪里,这时候就有了地址的概念,就类似于很多学生在很多个宿舍,可以一一对应

注意哦,本身程序是没有地址的,运行在内存中,内存才有地址的概念,c语言管这个地址,叫做指针!这就是指针的概念。

1byte = 8bit
1KB = 1024byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
1PB = 1024TB

内存中一个单位的是一字节,1byte,1字节相当于8个比特位(0或者1),也就是一个宿舍八个人,单位转换如上。

它是怎么拿取数据的?

底层如上图,当然会更复杂一点,这也是冯·诺依曼架构中CPU与内存的交互过程(cpu是计算的,内存是暂时放数据的)。我这里只是简单说说。

原理如下:

CPU会拿内存里的数据(通过控制总线说我要拿数据),

计算好再通过地址总线(0100011这种的地址,有的是32个有的是64个)找到对应的地址里的数据,

再通过数据总线拿回来这个数据,开始计算 。算完之后用控制总线(说我要放数据),通过地址总线找到对应的位置,然后用数据总线放进去数据。

指针在c语言中是怎么样的

如上图,int i=10;也就是在内存中开辟了四个字节的空间存放10,第一个地址是x00FCF94C,

剩下三个其实说不说都无所谓,因为只要知道第一个,剩下的地址+1就能找到了。

怎么拿到i的地址?

用&取地址符号,%p打印地址信息(每一次运行地址都不一样,基本上不会一样)

指针变量

c语言说的指针,其实是口语化,说的就是指针变量。指针是地址,指针变量存的是地址的变量

int main()
{
	int i = 10;
	int* p = &i;
	return 0;
}

int *p就是创建指针。这里的p是指针的名字,*说明它是一个指针,int是 他的基类型,也就是p指向的是int类型的变量的地址。如下图:

指针变量实际上是一种变量,它里面存的就是地址,p里面存的是i的地址(0x100),就是可以通过p找到i,我们形象的称为“p指向i”

解引用操作*

如果我们需要拿到i的值,不仅可以通过i本身拿,还能通过p指针拿。

我们通过*p来操作i,*p的意思 就是,我拿到p指向的对象的值,也就是10

我们不仅可以拿到值,我们甚至可以通过*p来修改值

*p就相当于一个代理人,只要高启强说一句,老莫我想吃鱼了。老莫就会帮强哥把人干掉。

还有一些比较有意思的题

int main()
{
	int* a, * b, * c;
	a = (int*)malloc(sizeof(int));
	b = c = a;
	*a = 3;
	*b = 4;
	*c = 5; printf("%d,%d,%d\n", *a, *b, *c);
	return 0;
}

答案输出5,5,5。为什么呢?我们画个图

在 b = c = a 中:也就是从右往左赋值,都是0x1000

  • a 存储的是malloc返回的地址(比如 0x1000)

  • c = a 意思是:把a的值(0x1000)赋给c

  • b = c 意思是:把c的值(0x1000)赋给b

所有指针存储的都是同一个地址值,没有"指向指针"的关系。

所以打印的时候,就是打印malloc区域的值,也就是5。

指针变量的大小

我们之前说地址总线有些是32位,有些是64位,这个就是看环境了,

在vs中,如果是x86环境,就是32位地址总线,如果是x64的就是64位地址总线。

1位就是一比特位(1bit),8bit=1字节,32bit=4个字节。所以在x86环境下,一个指针变量就是4个字节。下面都是4哈,跟基类型是int还是char无关。

int main()
{
    printf("%zd\n", sizeof(char *));//4
    printf("%zd\n", sizeof(short *));//4
    printf("%zd\n", sizeof(int *));//4
    printf("%zd\n", sizeof(double *));//4
    return 0;
}

既然都是4个字节,为什么还需要基类型?对指针同个操作结果是不同的。

指针基类型的意义

当我打印他们的地址时,pc和pi与&i是一样的。但是pc+1和pi+1就不一样呢。

为什么会不一样呢?因为计算机自动认为基类型就是你指向的那个东西的类型。

int*就是一次性跳四个字节,char*一次性跳一个字节,其他同理。

指针的类型决定了指针向前或者向后⾛⼀步有多⼤(单位)。

还有一个是void*,它可以来接收指针的地址,也是四个字节,但是它不能用来进行指针的加减。

指针的计算

指针+-整数

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = &arr[0];
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));//指针+整数
	}
	return 0;
}

先让p,也就是p的值(也就是地址,假设是0x100)让他+i,也就是加i个单位(基类型为int,四个字节),

第一个i=0,所以就是0x100,*p就是打开0x100的值(首元素地址),也就是1。

其次p+1,加一个单位变成0x104。

*p,也就是取0x104取它的值,也就是2,打印。同理下面也是如此。

指针-指针(不能相加)

//指针-指针
int my_strlen(char* start)
{
	char* p = start;
	while (*p != '\0')
	{
		p++;
	}
	return p - start;
}
int main()
{
	printf("%d", my_strlen("abcd"));
	return 0;
}

自己实现一个strlen函数,其实很简单。

我们用指针接收字符串,然后用一个新的指针,来遍历字符串,让指针p++(注意不是*p,但是*p++,相当于*(p++),对于这个程序来说,结果是一样的,但是意义可能有点出错),

如果=\0就算是没有值的,跳出循环。然后用p-start,也就是指针-指针。ptr2 - ptr1 = (地址差) / sizeof(类型)

我们可以把指针理解为日期,整数理解为天数。

指针+-整数=指针   ->  日期+-天数=日期

指针-指针=整数  ->      日期-日期=天数

指针相加无意义,可能值也可能无值。就像2024年某一天+2025年某一天,你也不知道是哪一天,甚至不知道发生什么,也不一定有人类了。

传值和传址

下面就是传值,函数开始,开辟临时空间,函数关闭,关闭临时空间。

所以就像武侠小说,知道真相的人调查完了,但是突然被暗杀了,也没有人知道真相了。

传值

传址

这种相当于借刀杀人,*px和*py就是直接对 地址映射 的值 进行修改。

就像身份证号对应的只有你,我不一定直接说我要找张三,我可以说我要找4415.....(张三的身份证号码),也可以找到张三。

数组传参的本质

形参int arr[]相当于int* arr,编译的时候会自动把int arr[]变成int*arr。

因为实参arr传进去,其实传的是一个地址,是arr首元素的地址,也就是&arr[0]。

&arr和arr和arr[0]有什么区别呢?

用来计算时,加加减减的单位会有所不同
int main()
{
	int arr[10] = { 0 };
	int* p=arr;
	printf("%d ", p[1]);//相当于arr[1]
	printf("%d ", *(p+1));//相当于*(arr+1)
	//且arr[1]相当于*(arr+1)
	return 0;
}

二级指针

与一级指针类似

指针数组和数组指针

指针数组:

是数组,存的是n个指针

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	//数组名是数组⾸元素的地址,类型是int*的,就可以存放在parr数组中
	int* parr[3] = { arr1, arr2, arr3 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("%d ", parr[i][j]);
		}
		printf("\n");
	}
}

数组指针

是指针存的是数组,用的很少,有特定的场景

int (*p)[10]=&arr;

它和指针数组很像,他加了括号,不加括号的话,p会跟[10]结合,变成指针数组。

如果加了括号,就是p和*结合,是个指针,指向的是10单位这个数组。

void test(int(*p)[5], int r, int c)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };
	test(arr, 3, 5);
	return 0;
}

数组指针那不是跟指针数组一样嘛?

不一样的,虽然看着很像,但是指针数组把数组每一行都指了一遍,但是数组指针只指向第一行。需要的时候数组指针++就变成第二行了。

数组指针其实很少用到,一般用于二维数组。

结构体

像数组,我们装的都是一样类型的,但是如果是不一样类型的该怎么放一起呢?用结构体。

声明如下:struct stu{};        注意要有分号

struct stu
{
	char name[10];
	int age;
};

咋用呢?

第一种,声明之后,定义结构体变量并且初始化

//第一种用法
struct stu
{
	char name[10];
	int age;
};
int main()
{
	struct stu s = { "zhangsan",18 };
	printf("name:%s,age:%d\n", s.name, s.age);
	return 0;
}

第二种,声明的时候直接定义变量并且初始化

//第二种用法
struct stu
{
	char name[10];
	int age;
}s = { "zhangsan",18 };
int main()
{
	printf("name:%s,age:%d\n", s.name, s.age);
	return 0;
}

第三种,先声明结构体,然后顺带整个结构体变量,之后你再赋值

//第三种用法
#include<string.h>
struct stu
{
	char name[10];
	int age;
}s;
int main()
{
	s.age = 18;
	strcpy(s.name, "zhangsan");
	printf("name:%s,age:%d\n", s.name, s.age);
	return 0;
}

注意,如果不是再定义的时候就初始化的话,那么你在赋值的时候,简单类型可以直接:变量名.    来赋值如果是字符串的话,就需要用到strcpy函数赋值,记得引头文件string.h。

结构体数组

struct stu
{
	char name[10];
	int age;
}a[2],arr[3] = { "lisi",2,"wangwu",4,"zhaoliu",8 };
int main()
{
	struct stu brr[3] = { "lisi",2,"wangwu",4,"zhaoliu",8 };
	printf("name:%s,age:%d\n", arr[0].name, arr[0].age);
	printf("name:%s,age:%d\n", arr[1].name, arr[1].age);
	return 0;
}

结构体指针

struct stu
{
	int num;
	char name[20];
	char sex;
	int age;
};
struct stu s[3] = { {1,"zhangsan",'M',18},{2,"lisi",'w',19}, {3,"wangwu",'M',20} };
int main()
{
	struct stu* p;
	printf(" No. Name     sex  age\n");
	for (p = s; p < s + 3;p++)
	{
		printf(" %d %-10s %2c %4d\n", p->num, p->name, p->sex, p->age);
	}
	return 0;
}

如果指针指向的是结构体,用指针引用成员变量,可以用->,如上。

大致如下,改了点数据

结构体传参

结构体传参传的是

也就是说name和age都是商品房,创建好给你的,你不要了就销毁了,类似于多次传简单类型吧。

struct Student {
    int id;
    char name[20];
    float score[3];
};

void change(struct Student stu) {
    stu.score[0] = 100;
    strcpy(stu.name, "Jerry");
}

int main() {
    struct Student stu;
    stu.id = 12345;
    strcpy(stu.name, "Tom");
    stu.score[0] = 67.5;
    stu.score[1] = 89;
    stu.score[2] = 78.6;
    change(stu);
// 此处stu的内容不会改变,因为change函数操作的是副本
    return 0;
}

结构体指针传参,会改变内容

struct Student {
	int id;
	char name[20];
	float score[3];
};
void change(struct Student* p) {
	p->score[0] = 100;
	strcpy(p->name, "Jerry");
}

int main() {
	struct Student stu;
	stu.id = 12345;
	strcpy(stu.name, "Tom");
	stu.score[0] = 67.5;
	stu.score[1] = 89;
	stu.score[2] = 78.6;
	change(&stu);
	// 此处stu的内容会改变,因为change函数操作的是原始结构体
	return 0;
}

结构体内存

在专升本中,其实并没有考的很复杂,比如

struct stu
{
    char a;
    int b;
};

它在专升本中,其实就是把变量都加起来,上面的代码就是char+int,1+4=5个字节。但是事实上它还要涉及到内存对齐,会浪费几块内存,专升本不考,只考简单的相加就行了,内存对齐有兴趣可以去翻阅其他博客。

还有就是,它在声明的时候并不会开辟空间给结构体,它只有定义了变量才会开辟空间给成员结构,而结构体本身没有空间,只有成员变量有空间。

共用体和枚举类型

这两个很简单,考的也是简单,可以说是考结构体顺带考一下的。搞清楚语法就行了。

共用体

union u
{
	int a;
	char b;
};

a和b共用一个地址,他们两的起始地址一样。共用体变量的内存长度是最长的成员的长度。

结构体和共用体区别

枚举

enum sex
{
	MALE,//0
	FAMALE//1
};
int main()
{
	printf("%d", MALE);//打印0
	return 0;
}

枚举类型中,第一个成员变量值为0,依次递增。如果把MALE赋值为4,FAMALE为5。

文件操作

文件函数

有大佬写了更好的文章,这里我直接引用:

大佬的文件操作文章

putchar和getchar以及scanf和printf还有gets和puts区别

此外,我这里想要补充的就是:

putchar和getchar以及scanf和printf还有gets和puts区别(这个在专升本中还是很经常用到)

这里有大佬的文章,写的比我好多了,请看:

大佬写的putchar和getchar以及scanf和printf还有gets和puts区别文章

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐