图文并茂对C语言基础复习进行详细总结,让各种知识点一目了然?

C语言

因为最近在准备考研,报考的学校需要考察c语言,所以我笼统的学习一下,加深印象。
大部分图片来自于王敬华老师的ppt,如果需要ppt,可以找我要。

1.C语言的基本结构

1.C语言函数的组成

C语言程序的函数由两部分:
(1)定义变量(变量代表数据),称为声明部分。
(2)代表操作,称为执行部分。
C语言和C++不同的之处,变量定义必须放到执行部分之前,顺序不能颠倒
如下图,在C语言中为错误格式(int b必须要写到a=10之前才为正确格式),但在c++中为正确格式
在这里插入图片描述

2.C语言的标识符

C语言的变量和函数都有自己的名字,它们必须是合法的标识符

1.命名要求

1.变量名只能是英文字母(A-Z,a-z)和数字(0-9)或者下划线_组成。
2.第一字母必须是字母或者下划线开头
3.变量名区分大小写。hello和Hello 不一样
4.不能使用关键字来命名变量。

2.关键字

在这里插入图片描述

3.常量

定义:程序运行时其值不能改变的量。
常量的分类:
(1)直接常量(值常量)
整数常量:10、15、-10、-30
实型常量:12.5、30.0、-1.5
字符常量:’A‘、’b‘、’c‘

(2)符号常量
用标识符代表常量
格式:
#define 符号常量 常量
定义要求:
1.行尾不能有分号
2.define前面一定要有#
3.符号常量名最好使用大写
4.符号常量名最好有意义

5.C语言程序的基本步骤

在这里插入图片描述

总结

  1. C语言是由多个函数构成。
  2. 每个C程序中有且只有个main函数。
  3. main函数是程序的入口和出口。
  4. 不使用行号,无程序行的概念。
  5. 程序中可使用空行和空格
  6. C程序格式常用锯齿形书写格式。
  7. C程序中可加任意多的注释。
  8. 引用C语言标准库函数,一般要用文件包含预处理命令将其头文件包含进来。
  9. 用户自定义的函数,必须先定义后使用
  10. 变量必须先定义后使用
  11. 变量名、函数名必须是合法的标识符
  12. 不能用关键字来命名变量和函数。
  13. 函数包含两个部分:声明部分和执行部分
  14. C语言的语句都是以分号结尾。

2.各种运算符、优先级和结合性

同优先级运算符依据结合性进行运算
优先级一般的人都能下图中看懂,但是结合性的话还是得有必须说明一下。

我们以赋值运算符为例:
我们可以从图中看成赋值运算符的结合顺序为从右往左。
例:
int a,b=1,c=2;
a=b=c;
然后求a的值,我们可以根据结合顺序从右往左,先把c赋值给b,然后把b赋值给a,所以a=2,错误思想如果你从左往右看的话a=1(这种是错误的)。

下面这幅图有一个错误的地方,因为来自百度,我也不好修改:
错误地方:三目运算符的结合性是自右向左的。
在这里插入图片描述
需要注意的知识点

  1. 5/2=2;5/2.0=2.5
  2. int p,i=2; p=++i+(++i);//p=8,i=4,因为++i的结合性是从右往左,而且++i的优先级高于+号,所以把i=4,然后4+4=8;
  3. a=35,a4,a+5;其中a=15,但是逗号表达式的值为20。求解思路:因为=赋值运算符的优先级高于,逗号运算符;所以先把3*5的值赋值给a,但是,号运算符的结合性是从左往右进行,所以逗号表达式的值为20。
  4. 当表达式中存在有符号类型和无符号类型时,按照左图中算术运算数据类型转换规则,所有的操作数都自动转换为无符号类型
    例如:
    unsigned int a = 30;
    int b = -130;
    b会自动转换为无符号类型,得到b等于4294967166。

数据类型转换

参考地址:http://c.biancheng.net/view/1775.html

1.自动转换

自动类型转换就是编译器默默地、隐式地、偷偷地进行的数据类型转换,这种转换不需要程序员干预,会自动发生。
例如:int a=2.5; 则a的值将是2,而不是2.5。
1.无符号短长度的数据类型转换为长长度的数据类型
高位直接补0即可
例如:unsigned char ch = 0xfc;
int b;
b = ch; //b的值将是0x00fc
2.有符号短长度的数据类型转换为长长度的数据类型
符号位向高位扩展
例如:int a=-2;
unsigned long u;
u=a; //u的值将是0xfffffffe
3.长长度的数据类型转换短长度的数据类型
直接截取长长度低位部分
4.长度相同的数据类型转换
数据按照原样复制即可

2.算术运算符中数据类型的转换规则

在这里插入图片描述
紫色的线必定的转换 蓝色的线是运算对象不同时的转换

在这里插入图片描述

3.强制转换

如果需要,程序员也可以自己在代码中明确地提出要进行类型转换,这称为强制类型转换。
(float) a; //将变量 a 转换为 float 类型
(int)(x+y); //把表达式 x+y 的结果转换为 int 整型
(float) 100; //将数值 100(默认为int类型)转换为 float 类型

4.自动类型转换 VS 强制类型转换

在C语言中,有些类型既可以自动转换,也可以强制转换,例如 int 到 double,float 到 int 等;而有些类型只能强制转换,不能自动转换,例如以后将要学到的 void * 到 int *,int 到 char * 等。

可以自动转换的类型一定能够强制转换,但是,需要强制转换的类型不一定能够自动转换。现在我们学到的数据类型,既可以自动转换,又可以强制转换,以后我们还会学到一些只能强制转换而不能自动转换的类型。

可以自动进行的类型转换一般风险较低,不会对程序带来严重的后果,例如,int 到 double 没有什么缺点,float 到 int 顶多是数值失真。只能强制进行的类型转换一般风险较高,或者行为匪夷所思,例如,char * 到 int * 就是很奇怪的一种转换,这会导致取得的值也很奇怪,再如,int 到 char * 就是风险极高的一种转换,一般会导致程序崩溃。

使用强制类型转换时,程序员自己要意识到潜在的风险。

3.数据类型及表示范围

数据类型决定:
1.数据占用内存字节数。
2.数据取值范围。
3.其上可进行的操作。
在这里插入图片描述
原图地址:http://baijiahao.baidu.com/s?id=1664369656399419394&wfr=spider&for=pc在这里插入图片描述
在这里插入图片描述

1.整数类型

整型常量
1.十进制整数:由0~9和正负号表示。如:123,-456,0;
2.八进制整数:由数字0开头,后跟数字0~7表示。如:0123;
3.十六进制整数:由0x或0X开头,后跟0~9,a到f,A到F。如:0x123,0XFF

整形常量的分类:
1.整型常量后面加字母l或者L,认为它是long int 型常量。如:123L,45l
2.整型常量后缀加U或u,认为它是unsigned无符号数。如:358u,0x38AU
3.前缀、后缀同时使用可表示各种类型的数。如0xA5Lu。表示十六进制无符号长整数A5(前缀为0X,后缀L和U)。

2.实型数据

1.十进制小数形式:由数字0~9和小数点组成。如:0.5
2.指数形式:一般形式为aEn。(其中a为十进制数,n为十进制整数,可以带符号,都不可缺少)如:a×10^5。
3.三种实数类型中,其精度是
float<double<=long double,没有long float。
在这里插入图片描述

3.字符型常量

定义:用单引号括起来的单个普通字符或转义字符。
字符常量的值:该字符的ASCII码值。
转义字符:反斜线后面跟一个字符或一个代码值表示。
在这里插入图片描述
我们以"\n\为例"
如下图其中\n表示换行,\n后面的\表示hello wordld 和1234是合在一起的,中间只是换行符来实现换行而已;如果这里只使用\n的会报错。
在这里插入图片描述
如果如下图所示:就直接使用\n,不用在到后面加上一个\
在这里插入图片描述

4.字符串常量

定义:用双引号括起来的字符序列
存储:每个字符串尾自动加一个'\0'作为字符串结束标志。
C语言没有字符串变量,用字符数组存放。

4.格式化输入、输出

1.printf函数

在这里插入图片描述

1.格式化控制符总结

原图地址:https://www.bilibili.com/read/cv1048075/
在这里插入图片描述
补充知识

  1. 使用%p读取地址数据。
  2. 其中L是有符号长整型,u是无符号整型,。在BC下,L占4字节,u占2字节;在V下,L和u都占4字节。
  3. %g或者%G:按照%f或%e中输出宽度比较短的一种格式输出。
2.有符号整数的输出

在这里插入图片描述
在这里插入图片描述

3.无符号整数的输出

在这里插入图片描述
在这里插入图片描述

4.实数的输出

在这里插入图片描述
在这里插入图片描述

5.字符和字符串的输出

在这里插入图片描述
在这里插入图片描述

6.辅助格式控制符小结

在这里插入图片描述
在这里插入图片描述
printf函数需要注意的地方
1.格式转换符中,除了X、E、G以外,其它均为小写。
2.printf函数不会进行不同数据类型之间的自动转换。例:float a=0.55,但以%d的形式输出时。

2.scanf 函数

在这里插入图片描述

3.字符数据的非格式化输入和输出

  1. getchar:从键盘读取一个字符,以回车键结束回显,函数原型所在头文件stdio.h

  2. putchar:在显示器上输出字符c
    函数int putchar(int c);

  3. getc:从流文件stream中读取一个字符信息,以回车键结束回显,函数原型所在头文件stdio.h
    说明:该函数带有一个函数stream,它是一文件指针,当流文件是stdin时,getc函数的功能与getchar函数的功能完全相同。也就是说,getc(stdin)与getchar是等价的。

  4. putc:将字符c输出道流文件stream。
    函数:int putc(int c,FILE *stream)

  5. getche:与getchar的功能基本相同,输入字符后就结束回显,函数原型所在头文件conio.h

  6. getch:接受一字符输入,输入字符后就结束不回显,函数原型所在头文件conio.h

  7. puts:将字符串string的所有字符输出到屏幕上,自动回车,函数原型所在头文件stdio.h
    函数:int puts(char *string);

  8. fflush:清楚键盘缓冲区,函数原型所在头文件stdio.h
    其中回显的意思就是:
    你从键盘输入的字符是否回显示出来
    如下图:
    在这里插入图片描述

我从键盘输入的是a和b,但是没有显示我输入b,因为getch不回显。
在这里插入图片描述

5.程序的控制结构及应用

程序=数据结构+算法
所以学好数据结构和算法很重要

1.C程序中语句的分类

  1. 表达式语句:表达式加上分号组成。
  2. 函数调用语句:由函数名、实际参数加上分号组成。
  3. 空语句:只有分号。
  4. 复合语句:用{…}括起来的一组语句。
  5. 控制语句:用来实现一定的控制功能的语句。例如:分支(if、switch)、循环(for、while、do~while)、赋值控制(continue、break、goto、return)。

2.关系运算符、逻辑运算符、条件运算符

1.关系运算符

在这里插入图片描述
关系表达式:用关系运算符连接起来的式子。
C语言用0表示假,非0表示真。
一个关系表达式的值不是0就是1。
关系运算符的优先级:
在这里插入图片描述

2.逻辑运算符

在这里插入图片描述
逻辑表达式:用逻辑运算符连接起来的式子。
在这里插入图片描述
逻辑运算注意:
逻辑表达式求解,不是所有的逻辑运算符都被执行。
&&只有左边为真时,才执行右边。
||只有左边为假时,才执行右边。
例如:
在这里插入图片描述
结果分析,因为a>b为假,所以m=0,因为&&必要左边为真时,才能运算右边的字符,所以n=c>d是不执行的。所以n=5。得到结果如下图:
在这里插入图片描述

3.条件运算符

一般形式: expr 1? expr2: expr3
条件运算符可嵌套:x>0?1:(x<0?-1:0)
优先级:13
结合方向:自右向左

补充说明if_else配对原则

C语言规定,在缺省{}时,else总是和它上面离它最近的未配对的if配对。

3.switch语句

1.基本执行过程

在这里插入图片描述

2.使用switch语句注意事项
  1. switch方面的"表达式",可以是int,char和枚举型中的一种,但不可为浮点型
  2. case后面语句组可加{}也可以不加{},但一般不加{}。
  3. 每个case后面"常量表达式"的值,必须各不相同,否则会出现相互矛盾的现象。
  4. 每个case后面必须是"常量表达式",表达式中不能包含变量。
  5. case后面的"常量表达式"仅起到语句标号作用,并不进行条件判断。系统一旦找到入口标号,就从词标号开始执行,不再进行标号判断,所以必须加上break语句,以便结束switch语句。
  6. 多个case子句,可共用同一语句,不加break的时候。
  7. case子句和default子句如果都带有break子句,那么它们之间顺序的变化不会影响switch语句的功能。
  8. case子句和default子句如果有的带有break子句,而有的没有break子句,那么它们之间顺序的变化可能会影响输出的结果。
  9. switch语句可以嵌套

4.while语句

while语句的注意事项 :

  1. 先判断表达式,再执行循环体
  2. while后面的括号不能省
  3. while后面的表达式可以是任意类型的表达式,但一般是条件表达式或逻辑表达式。
  4. 表达式的值是循环的控制条件。
  5. 语句部分称为循环体。
  6. 如果while后的表达式的值一开始就为假,循环体将一次也不执行。
  7. 循环体的语句可为任意类型的C语句。
  8. 当表达式为,或者遇到break、return或goto,则退出while循环。
  9. 循环控制变化必须初始化,而且要在while语句的某处改变循环控制变量,否则极易构成死循环。
  10. 允许while语句嵌套。

5.do_while语句

do_while语句的注意事项:

  1. 先执行循环体,再判断表达式
  2. while后面的括号不能省。
  3. while后面的分号不能省。
  4. while后面的表达式可以是任意类型的表达式,但一般是条件表达式或逻辑表达式。
  5. 表达式的值是循环的控制条件。
  6. 语句部分称为循环体。
  7. 如果do_while后的表达式的值一开始就为假,循环体还是要执行一次
  8. 循环体中的语句可为任意类型的C语句。
  9. 循环控制变化必须初始化,而且要在do_while语句的某处改变循环控制变量,否则极易构成死循环。
  10. do-while语句也可以组成多重循环,而且也可以和while语句相互嵌套。

6.for语句

for语句使用的注意事项:

  1. for后面的括号不能省。
  2. 表达式1:可以为任意表达式,一般为赋值表达式,给控制变量赋值。
  3. 表达式2:可以为任意表达式,一般为关系表达式或逻辑表达式,循环表达式。
  4. 表达式3:可以为任意表达式,一般为赋值表达式,给控制变量增量或减量。
  5. 表达式之间用分号隔开。
  6. 语句部分称为循环体。
  7. 表达式1、表达式2、和表达式3都是任选项,可以省掉其中的一个、两个或全部,但其用于间隔的分号是一个也不能省的。
  8. 表达式2如果为空则相当于表达式2的值为真,容易造成死循环。
  9. 可以嵌套。

7.break和continue、goto语句和exit函数

1.break语句

功能:在循环语句和switch语句中,终止并跳出循环体或开关体
说明:

  1. break不能用于循环语句和switch语句之外的任何其它语句之中。
  2. break只能终止并跳出最近一层的结构
2.continue语句

功能:结束本次循环,跳过循环体中尚未执行的语句,进行下一次是否执行循环体的判断。
说明:

  1. 仅用于循环语句中。
  2. 在嵌套循环的情况下,continue语句只对包含它的最内层的循环体语句起作用。
3.goto语句

一般形式:
在这里插入图片描述
作用:goto语句的作用是在不需要任何条件的情况下直接使程序跳转到该语句标号所标识的语句去执行。
说明:

  1. 语句标号是按标识符规定书写的符号,放在某一语句行的前面,标号后加冒号(:)。语句标号起标识语句的作用,与goto 语句配合使用。
  2. goto语句可与条件语句配合使用来实现条件转移,构成循环。
  3. 在嵌套循环的情况下,利用goto语句可以直接从最内层的循环体跳出最外层的循环体
4.exit(int status)函数

功能:终止整个程序的执行,强制返回操作系统。
说明:参数status为int型,status的值传给调用进程(一般为操作系统)。按照惯例,当status的值为0或为宏常量EXIT_SUCCESS时,表示程序正常退出;当status的值为非0或为宏常量EXIT_FAILURE时,表示程序出现某种错误后退出。
函数使用:
exit(-1);

6.字符串与数组,字符串常用函数

1.数组

1.定义说明
  1. 数组定义时,必须指定数组的大小,数组大小必须是整型常量表达式,不能是变量或者变量表达式,下标可以是整型常量、整型变量或整型表达式。
  2. 数组定义后,系统将给其分配一定大小的内存单元,其所占内存单元的大小与数组元素的类型和数组的长度有关;数组所占内存单元的字节数 = 数组大小 × sizeof(数组元素类型)
  3. 数组中每个数组元素的类型均相同,它们占用内存中连续的存储单元,其中第一个数组元素的地址是整个数组所占内存块的低地址,也是数组所占内存块的首地址,最后一个数组元素的地址是整个数组所占内存块的高地址(末地址);下标的最小值是0,最大值则是数组大小减1
  4. 只能逐个引用数组元素,不能一次引用整个数组。
  5. 数组引用要注意越界问题。
  6. 数组必须先定义,后使用
2.一维数组的初始化赋值
  1. “=”后面的表达式列表一定要用{ }括起来,被括起来的表达式列表被称为初值列表,表达式之间用“,”分隔
  2. 表达式的个数不能超过数组变量的大小;
  3. 表达式1是第1个数组元素的值,表达式2是第2个数组元素的值,依此类推。
  4. 如果表达式的个数小于数组的大小,则未指定值的数组元素被赋值为0
  5. 当对全部数组元素赋初值时,可以省略数组变量的大小,此时数组变量的实际大小就是初值列表中表达式的个数
  6. C语言除了在定义数组变量时用初值列表对数组整体赋值以外,无法对数组变量进行整体赋值,只能通过来赋值:
    1.C语句对数组中的数组元素逐一赋值。
    2.循环语句进行逐一赋值。
    3.memset函数来赋值。例:
    int a[10];
    memset(a,0,10sizeof(int));
    4.使用memcpy函数实现数组间的赋值。例:
    int a[5]={1,2,3,4,5},b[5];
    memcpy(b,a,5
    sizeof(int));

2.字符串和字符串数组

**字符串的本质:**是一种以'\0'结尾的字符数组。
下列赋值是否等价:

在这里插入图片描述
字符串数组
定义:构成数组的数据是字符串。
初始化:
其中:每个字符串的长度应小于n-1(因为字符串的结尾符’\0’占用一个单元)。
在这里插入图片描述

3.字符串常用函数

1.gets函数和scanf函数

格式:gets(字符数组) //应包含的.h文件为stdio.h
功能:从键盘输入一回车结束的字符串放入字符数组中,并自动加’\0’。
说明:输入串长度应小于字符数组维数。

#include<stdio.h>

int main(){
    char s[5]="hello";
    gets(s);
    printf("%c",s[4]);
    return 0;
}

效果:
在这里插入图片描述

格式:scanf("%s", 字符数组) //应包含的.h文件为stdio.h
功能:从键盘输入一以空格或回车结束的字符串放入字符数
组中,并自动加’\0’
说明:输入串长度应小于字符数组维数

在这里插入图片描述

2.puts函数和printf函数

格式:puts(字符串地址) //应包含的.h文件为stdio.h
功能:向显示器输出字符串(输出完,换行)
说明:如果是字符数组,则必须以’\0’结束

格式:printf("%s", 字符串地址) //应包含的.h文件为stdio.h
功能:依次输出字符串中的每个字符直到遇到字符’\0’,(’\0’不会被输出)

3.strlen函数

格式:strlen(字符串地址) //应包含的.h文件为string.h
功能:计算字符串长度
返值:返回字符串实际长度,不包括’\0’在内
在这里插入图片描述
答案:1、3、1

4.strcpy函数

格式:strcpy (字符数组1,字符串2) //应包含的.h文件为string.h
功能:将字符串2拷贝到字符数组1中去
返值:返回字符数组1的首地址
说明:

  1. 字符数组1必须足够大
  2. 拷贝时’\0’一同拷贝
  3. 不能使用赋值语句为一个字符数组赋值

例:
char str1[20], str2[20];
scanf ("%s", str2);
strcpy (str1, str2);

5.strncpy函数

格式:strncpy (字符数组1, 字符串2, 长度n) //应包含的.h文件为string.h
功能:将字符串2的前n个字符复制到字符数组1中去,并在末尾加’\0’
返值:返回字符数组1的首地址
说明:字符数组1必须足够大

例:
char str[20];
strncpy (str, “0123456789”, 5); //将“0123456789”的前5个字符复制到str中,并加’\0’
printf ("%s", str); //将输出01234

6.strcmp函数和stricmp函数,strncmp函数和strnicmp函数

strcmp函数
格式:strcmp (字符串1, 字符串2) //应包含的.h文件为string.h
功能:比较两个字符串
比较规则:对两串从左向右逐个字符比较(ASCII码),直到遇到不同字符或’\0’为止
返值:返回int型整数。a. 若字符串1< 字符串2, 返回负整数。

stricmp函数
格式:同strcmp //应包含的.h文件为string.h
差别:stricmp在比较两个字符串时不区分大小写,而strcmp区分大小写

strncmp函数
格式:strncmp (字符串1, 字符串2, 长度n) //应包含的.h文件为string.h
功能:将字符串1前n个字符的子串与字符串2前n个字符的子串进行比较
返值:同strcmp

在这里插入图片描述
在这里插入图片描述
strnicmp函数
格式:同strncmp //应包含的.h文件为string.h
差别:strnicmp在比较两个字符串时不区分大小写,而strncmp区分大小写。

7.strcat函数

格式:strcat (字符数组1, 字符数组2) //应包含的.h文件为string.h
功能:把字符数组2连接到字符数组1后面
返值:返回字符数组1的首地址
说明:
1.字符数组1必须足够大
2.连接前,两串均以’\0’结束;连接后,串1的’\0’取消,新串最后加’\0’。

8.常用的字符和字符串处理函数

在这里插入图片描述

7.函数参数的传递方式

1.函数的概述

函数其实就是一段可以重复调用的、功能相对独立完整的程序段。

C语言规定,对函数调用之前,必须对函数原型加以说明,否则会出现编译错误!

编写C程序的一般格式:
在这里插入图片描述

2.函数的定义和调用

1.无参数无返回值的函数

在这里插入图片描述
用途:此类函数用于完成某项固定的处理任务,执行完成后不向调用者返回函数值。

2.无参数有返回值的函数

在这里插入图片描述

3.有参数无返回值的函数

在这里插入图片描述
用途:此类型的函数主要是根据形参的值来进行某种事务的处理。灵活性上要比无形参的函数强,它更能体现调用函数与被调函数之间的数据联系。
原型声明:
在这里插入图片描述

4.有参数有返回值的函数

在这里插入图片描述
函数用途:此类型的函数主要是根据形参的值来进行某种事务的处理,同时可将处理后的结果值返回给调用函数 。它最能体现调用函数与被调函数之间的数据联系。

函数原型:
在这里插入图片描述

3.函数参数的传递方式

根据实参传递给形参值的不同,通常有值传递方式和地址传递方式

1.值传递方式

方式:函数调用时,为形参分配单元,并将实参的值复制到形参中;调用结束,形参单元被释放,实参单元仍保留并维持原值。

特点:
1.形参与实参占用不同的内存单元。
2.单向传递。

在这里插入图片描述

2.地址传递方式

方式:函数调用时,将数据的存储地址作为参数传递给形参。
特点:

  1. 形参与实参占用同样的存储单元。
  2. 双向传递
  3. 实参和形参必须是地址常量或变量。

可以用数组名和指针变量作为参数的地址传递。

用数组名作为函数参数时主要事项:

  1. 形参数组和实参数组的类型必须一致。
  2. 形参数组和实参数组长度可以不相同,因为在调用时只传送首地址而不检查形参数组的长度。
  3. 多维数组也可以作为函数的参数。

8.变量的作用域和生存期

作用域和生存期是从空间和时间的角度来体现变量的特性。

1.变量的作用域和生存期

按其作用域范围可分为两种:局部变量和全局变量

变量从被生成到被撤消的这段时间。实际上就是变量占用内存的时间。
按其生存期可分为两种:即动态变量和静态变量

2.局部变量作用域和生存期

定义:在函数内作定义说明的变量,也称为内部变量

作用域:仅限于函数内,离谱函数后不可再引用。
生存期:从函数被调用的时刻到函数返回调用处的时刻。

说明:

  1. 主函数main()中定义的变量也是局部变量,它只能在主函数中使用,其他函数不能使用。
  2. 形参变量属于被调用函数的局部变量;实参变量则属于全局变量或调用函数的局部变量。
  3. 允许在不同函数中使用相同的变量名。
  4. 在复合语句中定义的变量也是局部变量;其作用域只在复合语句范围内。其生存期是从复合语句被执行的时刻到复合语句执行完毕的时刻。

3.全局变量作用和生存期

作用:在函数外部作定义说明的变量,也称为外部变量 。它不属于哪一个函数,而属于一个源程序文件。
作用域:从定义变量的位置开始到本源文件结束,及有extern说明的其它源文件。
生存期:与程序相同。即从程序开始执行到程序终止的这段时间内,全局变量都有效。
说明:

  1. 应尽量少使用全局变量。
    (1)全局变量在程序圈出执行过程中始终占用存储单元。
    (2)降低了函数的独立性、通用性、可靠性及可移植性。
    (3)降低程序清晰性,容易出错。
  2. 若外部变量与局部变量同名,则外部变量被屏蔽。要引用全局变量,则必须在变量名前家上两个冒号"::"
  3. 全局变量定义必须在所有的函数之外,且只能定义一次,并可赋初始值。全局变量说明可定义多次,但是全局变量定义只能定义一次。
  4. 一般使用extern来对全局变量说明,可扩展全局变量的作用域。当extern定义时,赋了初值不是全局变量说明,而是全局变量定义。
    在这里插入图片描述

9.变量的存储类型

1.存储类型的说明和分类

概述:变量是对程序中数据的存储空间的抽象。
变量的属性:
数据类型:变量所持有的数据的性质,规定了它们的取值范围和可参与的运算。
存储类型:规定了变量占用内存空间的方式,也称为存储方式。分为静态存储和动态存储两种。

  1. 静态存储类型的变量是指在程序运行期间由系统分配固定的内存单元,并一直保持不变,直至整个程序结束,内存空间才被释放。前面所介绍的全局变量即属于此类存储方式。
  2. 动态存储类型的变量:
    用来存放变量以及进行函数调用时的现场信息和函数返回地址等,在这个区域存储的变量称之为动态变量,如形参变量、函数体内部定义的动态局部变量。对于存放于动态存储区的变量,是在函数调用开始时才分配动态存储空间,函数运行结束时释放这些空间。

2.4种变量的存储类型说明

在这里插入图片描述

1.自动变量(auto型变量)

说明:
存储类型说明符auto可以省略。
自动变量只能在函数内或复合语句种定义,它属于局部变量。

2.外部变量(extern型变量)

外部变量和全局变量是对同一类变量的两种不同角度的提法。全局变是是从它的作用域提出的,外部变量从它的存储方式提出的,表示了它的生存期。它属于静态存储类型

3.静态变量(static型变量)

静态变量和静态存储变量的异同

  1. 静态变量的类型说明符是static
  2. 静态变量属于静态存储类型。
  3. 静态存储类型的变量不一定就是静态变量。例如外部变量虽属于静态存储类型但不一定是静态变量,必须由static加以定义后才能成为静态外部变量,或称静态全局变量
  4. 自动变量可以用static定义它为静态自动变量,或称为静态局部变量,从而成为静态存储类型。

静态局部变量与自动变量之比较:

  1. 静态局部变量与自动变量均为局部变量
  2. 静态局部变量生存期长,为整个源程序。自动变量生存期短。
  3. 静态局部变量的生存期虽然为整个源程序,但是其作用域仍与自动变量相同
  4. 静态局部变量若在定义时未赋值初值,则系统自动赋初值0;但是自动变量不会赋初值0,而是赋值为随机数
  5. 静态局部变量赋初值只一次,而自动变量赋初值可能多次
    在这里插入图片描述
    静态全局变量:
    全局变量(外部变量)的说明之前再冠以static就构成了静态全局变量
    非静态的全局变量和静态全局变量的区别:
    全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。当一个源程序由多个源文件组成时,非静态的全局变量可通过外部变量说明使其在整个源程序中都有效。而静态全局变量只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能通过外部变量说明来使用它。
    在这里插入图片描述
4.寄存器变量(register型变量)

这种变量存放在CPU的寄存器中,使用时,不需要访问内存,而直接从寄存器中读写,这样可提高效率。
寄存器变量的说明符是register,属于动态存储类型

说明
只有局部自动变量和形式参数才可以定义为寄存器变量。
register修饰符是过时的修饰。

10.指针与动态内存分配

区分:内存单元的地址与内存单元中的数据是两个完全不同的概念。

变量地址:系统分配给变量的内存单元的起始地址
指针:一个变量的地址
指针变量:专门存放变量地址的变量

1.&与*运算符

两种关系:互为逆运算

&:取变量的地址,单目运算符,结合性为自右向左。
*:取指针所指向变量的内容,单目运算符,结合性为自右向左。

例:

int a=10;
int *p=&a;
printf("%d\n",p);  //指针变量,打印的为a的地址。
printf("%d\n",*p); //指针的目标变量,间接打印数据a
printf("%d\n",&p);  //打印的是指针变量p所占用内存的地址

2.指针变量的存取方法

直接访问:按变量名来存取变量值。
间接访问:通过存放变量地址的变量去访问变量;指针属于间接访问。

指针变量的定义的注意事项:

  1. int *p1,*p2;与int *p1,p2;
  2. 指针变量名是p1,p2,不是*p1,*p2。
  3. 指针变量只能指向定义时所规定类型的变量
  4. 指针变量定义后,变量值不确定,应用前必须先赋值。
  5. 指针变量的初始化赋值是赋值初始地址值
  6. 赋值变量的类型必须与指针变量类型相同。
  7. 不允许直接把一个数赋值给指针变量。
  8. 当在赋值语句中,指针变量不需要加*。例:p=&a;
  9. 不能用auto变量的地址去初始化static型指针。

3.零指针与空类型指针

零指针:
定义:指针变量值为0
说明:
p指向地址为0的单元,系统保证该单元不作它用,表示指针变量值没有意义。

用途:
1.避免指针变量的非法引用
2.在程序中常作为状态比较

空类型指针:
表示:void *p;
说明:表示不指定p是指向哪一种类型数据的指针变量
使用时要进行强制类型转换。

4.指针和地址运算

指针的加法和减法运算
p ± n 的值 = ADDR ± n * sizeof(ptype)
在这里插入图片描述

11.指针与数组

如果将数组的起始地址赋给某个指针变量,那么该指针变量就是指向数组的指针变量

1.一维数组

指针p + 1指向数组的下一个元素,而不是简单地使指针变量p的值+1。其实际变化为p+1*size(size为一个元素占用的字节数)。例如,假设指针变量p的当前值为2000,则p+1为2000+1*2=2002,而不是2001。

char str[10];
int k;
char *p;
p = str;
for (k = 0; k < 10; k++)  
    p[k] = 'A' + k; //也可写成*(p+k) = 'A' + k
    //等价于*p++='A'+k;

2.二维数组

当二维数组的首地址赋给指针变量p以后,则访问某个元素a[i][j]可用以下几种方式来代替:*(p+i*n+j)、p[i*n+j]、*(a[0]+i*n+j)
·二维数组名a不可赋值给一般指针变量p,只能赋值给指向二维数组的指针变量。p = a;是非法的,一般使用p=&a[0][0]来赋值。

如果将二维数组名a看成一个行地址(第0行的地址),则a+i代表二维数组a的第i行的地址,a[i]可看成一个列地址,即第i行第0列的地址。行地址a每次加1,表示指向下一行,而列地址a[i]每次加1,表示指向下一列。

在这里插入图片描述

a-----二维数组的首地址,即第0行的首地址
a+i-----第i行的首地址
a[i] 等价于 *(a+i)------第i行第0列的元素地址
a[i]+j 等价于*(a+i)+j -----第i行第j列的元素地址
*(a[i]+j) 等价于 *(*(a+i)+j)等价于 a[i][j]。

a+i = &a[i] = a[i] = *(a+i) = &a[i][0], 值相等,含义不同
a+i 等价于 &a[i],表示第i行首地址,指向行
a[i] 等价于 *(a+i) 等价于 &a[i][0],表示第i行第0列元素地址,指向列。

3.通过二维数组的行指针来访问二维数组

格式:
在这里插入图片描述
例如:
int (*p)[3];

对指向二维数组的行指针变量进行赋值一般形式为:

  1. 二维数组名+整型常数n。如: p=a+1;
  2. &二维数组名[整型常量n]。如:p=&a[1]。
  3. 如:p = a[0];或p = &a[0][0]都是错误的。

4.指针数组

在这里插入图片描述
例如:

char c[3] = {'a', 'b', 'c'};
char *p[3];

p[0] = &c[0];
p[1] = &c[1];
p[2] = &c[2];

指针数组与数组指针的比较:
指针数组中的p是数组名,不能赋值,数组指针的p是指针变量可以赋值。
在这里插入图片描述

12.指针与字符串

字符串的表现形式:字符数组和字符指针实现。

1.字符指针

字符指针初始化:把字符串的首地址赋值给字符指针变量
例如:
char *string;
string=“hello world”;

字符指针变量与字符数组变量的区分:
例如:
char *cp; 与char str[20];

  1. str由若干元素组成,每个元素放一个字符;而cp中存放字符串首地址
  2. char str[20]; str=“I love China!”; (×)
    char *cp; cp=“I love China!”; (√)
  3. str是地址常量;cp是地址变量
  4. cp接受键入字符串时,必须先开辟存储空间

字符指针变量使用注意事项:

  1. 当字符指针指向字符串时,除了可以被赋值之外,与包含字符串的字符数组没有什么区别,字符指针可以被字符串的函数使用;例如:char *pstr;pstr=“12345”;strlen(pstr);。
  2. 如果一个指针没有指向一个有效内存就被引用,则被称为“野指针”操作或空指针赋值。野指针操作尽管编译时不会出错,但很容易引起程序运行时表现异常,甚至导致系统崩溃。

13.内存分配

1.静态内存分配

当程序中定义变量或数组以后,系统就会给变量或数组按照其数据类型及大小来分配相应的内存单元,这种内存分配方式称为静态内存分配

例如:
int k; //系统给变量k分配4个字节
char ch[10]; //给数组ch分配10个字符的存储块。

静态内存分配一般是已知道数据量大小的情况。

2.动态内存分配

所谓动态内存分配是指在程序运行过程中,根据程序的实际需要来分配一块大小合适的连续的内存单元

1.malloc函数

函数malloc()用于分配若干个字节的内存空间,返回一个指向该存储区地址的指针。
在这里插入图片描述

说明:
1.size这个参数的含义是分配的内存的大小(以字节为单位)。
2.返回值:失败,则返回值是NULL(空指针)。 成功,则返回值是一个指向空类型(void)的指针(即所分配内存块的首地址)。

例如:

int n, *pscore;
scanf ("%d", &n);
//分配n个连续的整型单元,首地址赋给pscore
pscore = (int *) malloc(n * sizeof(int));  

关于malloc的使用的注意事项:

  1. malloc前面必须要加上一个指针类型转换符,如前面的(int *)。因为malloc的返回值是空类型的指针,一般应与右边的指针变量类型一致。
  2. malloc所带的一个参数是指需分配的内存单元字节数,尽管可以直接用数字来表示,但一般写成如下形式:分配数量*sizeof(内存单元类型符)
  3. malloc可能返回NULL,表示分配内存失败,因此一定要检查分配的内存指针是否为空,如果是空指针,则不能引用这个指针,否则会造成系统崩溃。所以在动态内存分配的语句的后面一般紧跟一条if语句以判断分配是否成功。

2.calloc函数

函数calloc()用于给若干个同一类型的数据项分配连续的存储空间,其中每个数据项的长度单位为字节。通过调用函数calloc()所分配的存储单元,系统将其自动置初值0

在这里插入图片描述
说明:

  1. num表示向系统申请的内存空间的数量size表示申请的每个内存空间的字节数
  2. 如果分配内存失败,将返回空指针(NULL);如果成功,将返回一个void类型的连续存储空间的首地址。

例如:

int n, *pscore;
scanf ("%d", &n);
//分配n个连续的整型单元,首地址赋给pscore
pscore = (int *) calloc(n , sizeof(int));  

3.realloc函数

函数realloc()用于改变原来分配的存储空间的大小
功能:将指针p所指向的存储空间的大小改为size个字节
在这里插入图片描述

4.动态内存释放free函数

把内存进行释放
在这里插入图片描述

14.指针与函数

1.多级指针

定义:指向指针的指针
一级指针:指针变量中存放目标变量的地址
二级指针:指针变量中存放一级指针的地址

二级指针与指针数组的关系:
二级指针与指针数组的关系:int **p 与 int *q[10]:

  1. 指针数组名是二级指针常量。
  2. p=q;p+i=q[i]的地址
  3. 指针数组作形参时,int *q[ ]与int **q完全等价;但作为变量定义两者不同。
  4. 系统只给p分配能保存一个指针值的内存区;而给q分配10块内存区,每块可保存一个指针值。

2.指针作为函数的参数

参数传递方式:传值调用和传址调用
传值调用:将实参数值传递给形参。实参和形参占用各自的内存单元,互不干扰,函数中对形参值得改变不会改变实参的值,属于单向数据传递方式
在这里插入图片描述

传址调用:将实参的地址传递给形参。形参和实参占用同样的内存单元,对形参值得改变也会改变实参的值,属于双向数据传递方式。
在这里插入图片描述

3.指针型参数常态的使用

两种含义:

  1. 形参指针本身是常态(int *const p);则*p=5;正确,但是int a[10];p=a;将引起错误;只能修改值
  2. 指形参指针所指向的内存单元是常态(const int *p);则int a[10];p=a;正确,但是*p=5错误;只能修改地址

如果程序想通过函数为指针变量分配内存,则形参必须是一个指针的指针(即二级指针)。

4.指针作为函数的返回值

在这里插入图片描述
如果一个函数返回一个指针,不能返回auto型局部变量的地址,但可以返回static型变量的地址。

编写一指针函数常采用的方法有两种:
一种是在函数中使用动态内存分配(malloc),以其首地址返回;
另一种就是在函数中使用静态(static)变量或全局变量,以其存储单元的首地址返回。

5.函数指针

函数在编译时被分配的入口地址,用函数名表示
在这里插入图片描述
**区分int (p)(int,int)和int p(int,int):
例如:
int (*p)(int, int); 则定义了一个可指向带两个int型的形参,其返回值int型的函数指针
int *p(int, int); 则表示是一个返回值为int型指针函数

函数指针的赋值:
在这里插入图片描述
函数指针的调用:
在这里插入图片描述
例如:

int max (int a, int b)
{
   return (a > b? a : b);
}
int (*p)(int, int);  //定义函数指针p
p = max;    //函数指针的赋值
p(2, 3);(*p) (2, 3);   //函数指针的调用,等价于max (2, 3)

15.宏定义

1.预处理命令简介

预处理命令:C源程序中以#开头、以换行符结尾的行。
种类:
宏定义: #define、#undef
文件包含: #include
条件编译:#if、#ifdef、#else、#elif、#endif等。
其他:#line、#error、#program等。

格式:

  1. ”#“开头
  2. 占单独书写行
  3. 语句尾不加分号

2.宏定义

宏定义分为两种:不带参数的宏定义和带参数的宏定义

1.不带参数的宏定义:

在这里插入图片描述
功能:用指定标识符(宏名)代替字符序列(宏体)
在这里插入图片描述
宏替换时仅仅是将源程序中与宏名相同的标识符替换成宏的内容文本,并不对宏的内容文本做任何处理。

宏定义的注意事项:

  1. C程序员通常用写字母来定义宏名,以便与变量名区别。

  2. 宏定义的位置任意,但一般放在函数外面。

  3. 宏定义时,如果单词串太长,需要写多行,可以在行尾使用反斜线”\“续行符。

  4. 宏名的作用域是从#define定义之后直到该宏定义所在文件结束。

  5. #undef可终止宏名作用域。
    在这里插入图片描述

  6. 宏定义可以嵌套定义,但不能递归定义。

  7. 程序中字符串常量即双引号中的字符,不作为宏进行宏替换操作。

  8. 宏定义一般以换行结束,不要用分号结束,以免引起不必要的错误。

  9. 宏可以被重复定义

  10. 在定义宏时,如果宏是一个表达式,那么一定要将这个表达式用()括起来,否则可能会引起非预期的结果。

2.带参数的宏定义

在这里插入图片描述
在这里插入图片描述

宏展开:形参用实参换,其它字符保留。
宏体及各形参外一般应加括号()

带参的宏与函数区别:
在这里插入图片描述

3.文件包含

功能:一个源文件可将另一个源文件的内容全部包含气进来。
一般形式:
在这里插入图片描述
例如:
#include “head.h”

4.条件编译

根据一定的条件去编译源文件的不同部分,这就是条件编译。

在这里插入图片描述
条件编译与分支语句二者之间的差别:
1.条件编译是将满足编译条件的程序代码进行编译生成目标代码不满足编译条件的程序代码将不进行编译;而分支语句则是不管满足条件的代码,还是不满足条件的代码,都要编译生成目标代码(包括分支语句本身),所以如果用条件语句来代替条件编译命令,程序的目标代码将变长 。

  1. 条件编译命令可以放在所有函数的外部,也可以放在某函数的内部;但分支语句只能出现在某函数内部
  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值