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程序用整数1
和0
表示
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;