C

C

Posted by Wh0ami-hy on September 12, 2022

1. C程序运行过程

cpp 编译-> obj 连接-> exe

2. 其他

sizeof

sizeof操作符以字节形式给出了其操作数的存储大小

C语言中,每个变量和函数都有两个属性:数据类型和数据的存储类别

把 建立存储空间的声明 称为定义,不需要 建立存储空间的声明 称为为声明

注意

定义了变量的类型后,在程序的输入、输出及数据运算时,一定要注意数据的类型,否则会出现无法预料的错误

2.1. 变量

内存中的一段存储空间

2.1.1. 初始化

内存中存在以前其他程序留下的值,即垃圾数据,若不初始化,变量会被填充垃圾数据

2.1.2. 局部与全局

C语言中的变量,按作用域范围可分为两种,即局部变量和全局变量

  • 局部变量也称为内部变量。局部变量是在函数内作定义说明的。其作用域仅限于函数内, 离开该函数后再使用这种变量是非法的。在复合语句中也可定义变量,其作用域只在复合语句范围内。
  • 全局变量也称为外部变量,它是在函数外部定义的变量。它不属于哪一个函数,它属于一个源程序文件。其作用域是整个源程序。

如果在同一个源文件中,全局变量和局部变量同名,在局部变量的作用范围内,局部变量有效,全局变量被屏蔽,即不起作用

为了便于区分,习惯将全局变量的第一个字母大写

2.1.3. 变量的周期

C语言根据变量的生存周期来划分,可以分为静态存储方式和动态存储方式

  • 静态存储方式,是指在程序运行期间分配固定的存储空间的方式。静态存储区中存放了在整个程序执行过程中都存在的变量,如全局变量。
  • 动态存储方式,是指在程序运行期间根据需要进行动态的分配存储空间的方式。动态存储区中存放的变量是根据程序运行的需要而建立和释放的,通常包括:函数形参、自动变量、函数调用时的现场保护和返回地址等

2.1.4. 存储类别

C语言中存储类别又分为四类

  • 自动(auto)

用关键字auto定义的变量为自动变量,auto可以省略,属于动态存储方式

  • 静态(static)

用static修饰的为静态变量,如果定义在函数内部的,称之为静态局部变量。如果定义在函数外部,称之为静态外部变量。

静态局部变量属于静态存储类别,在静态存储区内分配存储单元,在程序整个运行期间都不释放。静态局部变量在编译时赋初值,即只赋初值一次。如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0或空字符。

当我们需要保留函数上一次的运行结果时,可以使用局部静态变量

  • 寄存器的(register)

注意:只有局部自动变量和形参可以作为寄存器变量。一个计算机系统中的寄存器数目有限,不能定义任意多个寄存器变量。局部静态变量不能定义为寄存器变量

  • 外部的(extern)

用extern声明的的变量是外部变量,外部变量的意义是某函数可以调用在该函数之后定义的变量

2.2. 常量

2.2.1. 常量在C中的表示

十进制
十六进制 前面加 0x
八进制 前面加 0

2.2.2. 常量在计算机中的存储

整数 以补码的形式
实数 以IEEE754标准化存储
字符 本质是整数,转为ASCII码

2.2.3. 字符常量

单个字符

''

字符串

""

2.3. 数据类型关键字

2.3.1. 基本数据类型

  • void:声明函数无返回值或无参数,声明无类型指针,显式丢弃运算结果
  • char:字符型类型数据,属于整型数据的一种
  • int:整型数据,通常为编译器指定的机器字长
  • float:单精度浮点型数据,属于浮点数据的一种
  • double:双精度浮点型数据,属于浮点数据的一种

2.3.2. 类型修饰关键字

  • short:修饰int,短整型数据,可省略被修饰的int。
  • long:修饰int,长整形数据,可省略被修饰的int。
  • signed:修饰整型数据,有符号数据类型
  • unsigned:修饰整型数据,无符号数据类型

2.3.3. 复杂类型关键字

  • struct:结构体声明
  • union:共用体声明
  • enum:枚举声明
  • typedef:声明类型别名
  • sizeof:得到特定类型或特定类型变量的大小

2.3.4. 存储级别关键字

  • auto:指定为自动变量,由编译器自动分配及释放。通常在栈上分配
  • static:指定为静态变量,分配在静态变量区,修饰函数时,指定函数作用域为文件内部
  • register:指定为寄存器变量,建议编译器将变量存储到寄存器中使用,也可以修饰函数形参,建议编译器通过寄存器而不是堆栈传递参数
  • extern:指定对应变量为外部变量,即在另外的目标文件中定义,可以认为是约定由另外文件声明的对象的一个“引用“
  • const:与volatile合称“cv特性”,指定变量不可被当前线程/进程改变(但有可能被系统或其他线程/进程改变)
  • volatile:与const合称“cv特性”,指定变量的值有可能会被系统或其他进程/线程改变,强制编译器每次从内存中取得该变量的值

2.4. 流程控制关键字

2.4.1. 跳转结构

  • return:用在函数体中,返回特定值(或者是void值,即不返回值)
  • continue:结束当前循环,开始下一轮循环
  • break:跳出当前循环或switch结构
  • goto:无条件跳转语句

2.4.2. 分支结构

  • if:条件语句
  • else:条件语句否定分支(与if连用)
  • switch:开关语句(多重分支语句)
  • case:开关语句中的分支标记
  • default:开关语句中的“其他”分治,可选。

2.4.3. 循环结构

  • for:for循环结构,for(1;2;3)4;的执行顺序为1->2->4->3->2…循环,其中2为循环条件
  • do:do循环结构,do 1 while(2);的执行顺序是1->2->1…循环,2为循环条件
  • while:while循环结构,while(1) 2;的执行顺序是1->2->1…循环,1为循环条件

以上循环语句,当循环条件表达式为真则继续循环,为假则跳出循环。

2.5. 数据类型

2.5.1. 基本类型

整型

int 占4字节

字符型

char 占1字节

实型

  • 单精度
float 占4字节
  • 双精度
double 占8字节

2.5.2. 构造类型

枚举类型

数组类型

结构体类型

共用体类型

2.5.3. 指针类型

2.5.4. 空类型

2.6. 类型转换

强制类型转换(数据类型) (表达式)

(float)(a)

2.7. 宏定义

宏名一般用大写

不带参数

#define 标识符 字符串

#define P 3

带参数

带参数的宏展开,只是将语句中的宏名后面括号内的实参代替#define命令行中的形参

#define S(r) r*r

如
S(a) = a*a
S(a + b) = a + b * a + b 而不是 (a + b)*( a + b)

2.8. 输入输出

2.8.1. 输出

printf("abcd");
格式符 说明
%p 将地址以十六进制输出
%d 输出十进制整数
%ld 输出长整型
%f 输出单精度浮点数
%lf 输出双精度浮点数
%c 输出单个字符
%s 输出字符串
%x 输出十六进制
%X 输出十六进制
%#X 输出十六进制,前面带0x

2.8.2. 输入

&地址符

int a=0;
scanf("%d",&a);

2.9. 运算符

2.9.1. 算术运算符

  • /

如果相除的两个数都是整数的话,则结果也为整数,小数部分省略。如果两数中有一个为小数,结果则为小数

  • %

取余运算的对象必须是整数

2.9.2. 自增减运算符

  • i++

先取值,再加

	int a = 10;
	printf("%d\n",a++);
//结果是 10
	int a = 10;
	a++;
//结果是 11
  • ++i

先加,再取值

	int a = 10;
	printf("%d\n",++a);
//结果是 11
	int a = 10;
	++a;
//结果是 11

2.9.3. 赋值运算符

2.9.4. 关系运算符

关系表达式的值是,在C程序用整数10表示

2.9.5. 逻辑运算符

  • &&

与运算。左边表达式为假时,右边表达式不会执行

  • ||

或运算。左边表达式为真时,右边表达式不被执行

  • !

非运算。参与运算的变量为真时,结果为假。参与运算量为假时,结果为真。例如 !(5>8),运算结果为真。

2.9.6. 三目运算符

表达式1 ? 表达式2 : 表达式3;

先判断表达式1的值是否为真,如果是真的话执行表达式2,如果是假的话执行表达式3

3>2 ? printf(">\n"):printf("<\n");

2.10. 选择结构

2.10.1. if

2.11. 循环结构

2.11.1. for

for(表达式1;表达式2;表达式3){
//代码块
}
  • for循环中的表达式1、2、3均可不写为空,但两个分号;;不能缺省
  • 省略表达式1(循环变量赋初值),表示不对循环变量赋初始值
  • 省略表达式2(循环条件),不做其它处理,循环一直执行死循环
  • 省略表达式3(循环变量增减量),不做其他处理,循环一直执行(死循环)
  • 表达式1可以是设置循环变量的初值的赋值表达式,也可以是其他表达式
  • 表达式1和表达式3可以是一个简单表达式也可以是多个表达式以逗号分割

2.11.2. while

先判断,后执行

while(){

}

2.11.3. do while

先执行,后判断。因此,do while循环至少要执行一次循环语句

do{

}while();	//使用do-while结构语句时,while括号后必须有分号

2.12. 分支结构

2.12.1. switch

  • 在case后的各常量表达式的值不能相同,否则会出现错误
  • 在case子句后如果没有break,那么一旦找到入口,则会一直往后执行。
  • switch后面的表达式语句只能是整型或者字符类型
  • 在case后,允许有多个语句,可以不用{}括起来
  • 各case和default子句的先后顺序可以变动,而不会影响程序执行结果
  • default子句可以省略不用
	switch(表达式){
	case 常量表达式1 : 执行代码块1 ; break;
	case 常量表达式2 : 执行代码块2 ; break;
	case 常量表达式3 : 执行代码块3 ; break;
	....
     default : 执行代码块;
	}

2.13. 结束语句

2.13.1. break

  • break用于循环,则是用来终止循环
  • break用于switch,则是用来跳出switch
  • break跳出的是最近的那层循环语句或switch语句
  • 在没有循环结构的情况下,break不能用在单独的if else语句中

2.13.2. continue

  • 不终止整个循环,只是提前结束本次循环,接着执行下次循环
while(1){
    A;
    B;
    continue;
    C;
}
//A、B会被执行,但 C 不会被执行

2.14. 数组

2.14.1. 定义

在程序中是一块连续的,大小固定并且里面的数据类型一致的内存空间

数据类型 数组名称[长度];
  • 数组的下标均以0开始
  • 定义数组时,数组长度必须为一个整型常量
  • 数据类型 数组名称[长度n] = {元素1,元素2…元素n};,采用这种初始化方式,元素个数小于数组的长度时,多余的数组元素初始化为0
int a[5]={0};
//数组中所有元素都被初始化为 0 

2.14.2. 数组长度

int length = sizeof(arr)/sizeof(arr[0]);

2.14.3. 二维数组

在C语言中,数组在内存中是按行存放的即从左到右一行存完再存下一行,所以二维数组必须指定列数,行数可以省略

数据类型 数组名称[行数][列数];

赋值

	int a[3][2]=
	{
	{1,2},{4,5},{8,9}
	};

2.15. 字符串

C语言中,是没有办法直接定义字符串数据类型的,但是我们可以使用字符数组来定义字符串

2.15.1. 特点

  • C中字符串以 \0 结尾,ASCII码中为NUL,十进制值是 0

2.15.2. 赋值

  • char 字符串名称[长度] = {'字符1','字符2',...,'字符n','0'};
  • char 字符串名称[长度] = "字符串值";char 字符串名称[长度] = {"字符串值"};
[]中的长度是可以省略不写的
采用第1种方式的时候最后一个元素必须是'0'或'\0',表示字符串的结束标志
采用第2种方式的时候,因为使用了双引号"",所以字符串末尾会自动加入 \0

2.15.3. 输入输出

  • 输入
char str[5];
scanf("%s",str);	//使用 %s,不用 &地址符
  • 输出
char str[5];
printf("%s",a);
  • gets(arr)

输入一个字符串

char a[5];
gets(a);
  • puts()

输出一个字符串

char a[5];
puts(a);

2.15.4. 字符串函数

函数名 说明 例子
strlen(s) 获得字符串的长度(字节为单位) strlen(“abc”),结果为3
strcmp(s1,s2) 比较字符串 strcmp(“abc”,”abc”),结果为-1
strcpy(s1,s2) 字符串拷贝 strcpy(“abc”,”abc”)
strcat(s1,s2) 字符串拼接 strcat(“abc”,“abc”)
atoi(s1) 字符串转为整数 atoi(“100”),结果为100
  • strlen()获取字符串的长度,在字符串长度中是不包括末尾的\0,而且汉字和字母的长度是不一样的
  • strcpy()拷贝之后会覆盖原来字符串且不能对字符串常量进行拷贝

2.16. 函数

2.16.1. 定义

返回值类型 函数名(形参列表){
//代码
    return 返回值或表达式
}

2.16.2. return

  • 终止被调函数,向主调函数返回值
  • 没有返回值的函数,返回类型为 void
  • return 后的语句可为空,可以是具体值,也可以是一个表达式
  • 函数返回值的类型和函数定义中函数的类型应保持一致,如果两者不一致,则以函数返回类型为准,自动进行类型转换

2.16.3. 函数调用

函数名(参数)

2.16.4. 函数声明

如果自定义的函数放在main函数后面的话,需要在main函数之前先声明自定义函数

数据类型 函数名(形参列表)

2.16.5. 参数

  • 形参只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在函数内部有效

  • 实参可以是常量、变量、表达式、函数等

无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值等办法使实参获得确定值。
  • 在参数传递时,实参和形参在数量上,类型上,顺序上应严格一致,否则会发生类型不匹配的错误

  • 数组名做参数,C语言并不检查形参数组的大小,只是将实参数组的首元素的地址传给形参数组名

2.17. 指针

2.17.1. 定义

  • 指针是一种保存变量在内存中地址的变量
  • 一个指针变量,无论其所指向的变量占几个字节,指针变量本身只占4个字节
int *p;	//声明一个 int 类型的指针 p

*的含义,运算符 *是间接寻址或者间接引用运算符。当它作用于指针时,将访问指针所指向的对象


int a=5;
int *p = &a;	//将变量a的地址赋值给指针变量p,这时 访问 *p 就是访问变量a
printf("%d",*p);

2.17.2. 优点

  • 指针的使用使得不同区域的代码可以轻易的共享内存数据
  • C语言中一些复杂的数据结构往往需要使用指针来构建,如链表、二叉树等
  • C语言是传值调用,而有些操作传值调用是无法完成的,如通过被调函数修改调用函数的对象,但是这种操作可以由指针来完成,而且并不违背传值调用

2.17.3. NULL指针

NULL 指针,表示不指向任何东西。可以通过给一个指针赋一个零值来生成一个 NULL 指针。

在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是为操作系统保留的。但是,内存地址 0 有一个特别重要的意义,它表明该指针不指向一个可访问的内存位置。

int *p = NULL;	
int *p = 0;	
//二者等价
printf("%p",p); //都是00000000

2.17.4. 指针的运算

  • 指针 +/- 整数

表示指针所指地址的前进或后退

int *p = 0;
printf("%p\n",p);	//是00000000
p++;
printf("%p\n",p);	//是00000004

//因为 指针变量p是int类型的,所以它的下一个地址要在现有的地址上加 4 
  • 指针 - 指针

表示两指针在内存中的距离。前提是两指针指向同一数组中的不同元素,距离的单位是数组元素的长度,不是字节

2.17.5. 指针和数组

数组名代表数组元素的首地址

数组中的[]本质是变址运算符(形同汇编中)

要掌握指针指向的数组元素的地址的计算方式

用指针访问一数组

int a[10];
int *p;
p = a;
  • p不变
*(p+i)
*(a+1)
a[i]
p[i]
上式等价
  • p后移
p++;

用指针访问二数组

一个二维数组a[3][4]可以看做是一个一维数组a,这个一维数组共有3个行元素,只不过这个一维数组a[3]的每个元素又是一个数组,a[0]a[1]a[2]是他们的数组名

a[0]  ==>  a[0][0] a[0][1] a[0][2] a[0][3] 
a[1]  ==>  a[1][0] a[1][1] a[1][2] a[1][3] 
a[2]  ==>  a[2][0] a[2][1] a[2][2] a[2][3]  

a[0]  ==>  a[0] + 0 代表a[0][0] 
a[1]  ==>  a[1] + 1 代表a[1][1]
a[2]  ==>  a[2] + 2 代表a[2][2]

二维数组名a是指向行的,一维数组名a[0]a[1]a[2]是指向列的。所以a代表的是首行的首地址,a[0]代表首行中的第一个元素的地址即a[0][0]的地址

    int a[3][4] = { 
        {0, 1, 2, 3}, 
        {4, 5, 6, 7}, 
        {8, 9, 10, 11} 
    };
    int (*p)[4] = a;	// 指针变量p指向包含4个整型元素的一维数组
    printf("%d\n",**p);

2.17.6. 指针和字符串

可以不定义字符数组,只定义一个字符指针变量,用它指向字符串常量中的字符。

注意:C语言中只有字符串常量,没有字符串变量

char *String = "I Love China";
//只是把第一个字符 I 的地址赋值给指针变量 String
printf("%s\n",String);	//输出

2.17.7. void指针类型

C中允许使用void指针类型,可理解为指向空类型不指向确定的类型的数据,在将它的值赋给另一指针变量时由系统自动为它进行类型转换

2.18. 动态内存分配

2.18.1. 堆

自由存储区,随用随分配。用malloc函数申请,使用完,用free函数释放

2.18.2. 栈

动态存储区,如局部变量、实参传递

2.18.3. malloc函数

void *malloc(unsigned int size);

在堆中分配一个长度为size的连续空间,返回值是所分配区域的第一个字节的地址。如果函数未能执行成功,如内存不足,则返回空指针NULL

2.18.4. free函数

void free(void *p);

释放指针变量p所指向的动态空间

2.19. 结构体

不同类型的数据来构成一个整体

struct是关键字,是结构体类型的标志

2.19.1. 结构体变量的定义

定义一个名字为Student的结构体类型,并非定义了一个结构体变量,就像int一样,只是一种类型。

定义结构体类型,只是说明了该类型的组成情况,并没有给它分配存储空间,就像系统不为int类型本身分配空间一样。只有当定义属于结构体类型的变量时,系统才会分配存储空间给该变量

习惯将结构体名的第一个字母大写

struct Student{
	int age;
	int grade;
	char *Name;
};
  • 先定义结构体类型,再定义变量
struct Student{
	int age;
	int grade;
	char *Name;
};
struct Student s1;//定义了一个结构体变量,变量名为s1。struct和Student是连着使用的。
  • 定义结构体类型的同时定义变量
struct Student{
	int age;
	int grade;
	char *Name;
}s2;		//结构体变量名为s2
  • 直接定义结构体类型变量,省略类型名
struct{
	int age;
	int grade;
	char *Name;
}s3;	//结构体变量名为s3

2.19.2. 结构体的初始化

只能在定义变量的同时进行初始化赋值,初始化赋值和变量的定义不能分开

将各成员的初值,按顺序地放在一对大括号{}中,并用逗号分隔,一一对应赋值

struct Student{
	int age;
	int grade;
	char *Name;
};

struct Student s2 = {18,99,"张三"};

2.19.3. 访问结构体变量的成员

  • 结构体变量名.成员名
struct Student{
	int age;
	int grade;
	char *Name;
};

struct Student s2 = {18,99,"张三"};

printf("%d\n",s2.age);
  • 指针变量名->成员变量名
struct Student{
	int age;
	int grade;
	char *Name;
};	
struct Student s2 = {18,99,"张三"};
struct Student *s = &s2; 
printf("%d\n",s->age);

2.19.4. 指向结构体的指针

  • 定义结构体类型的同时定义指针变量
struct Student{
	int age;
	int grade;
	char *Name;
}s2,*s;	//定义了一个结构体变量s2和结构体指针变量s
  • 先定义结构体类型,再定义指针变量
struct Student{
	int age;
	int grade;
	char *Name;
};
struct Student *s; 

本站总访问量