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语言程序的基本步骤
总结
- C语言是由
多个函数
构成。 - 每个C程序中有且只有
一
个main函数。 main
函数是程序的入口和出口。- 不使用
行号
,无程序行的概念。 - 程序中可使用
空行和空格
。 - C程序格式常用
锯齿
形书写格式。 - C程序中可加
任意
多的注释。 - 引用C语言标准库函数,一般要用文件包含预处理命令将其
头文件
包含进来。 - 用户自定义的
函数
,必须先定义后使用
。 变量
必须先定义后使用
。- 变量名、函数名必须是
合法的标识符
。 不能
用关键字来命名变量和函数。- 函数包含两个部分:
声明部分和执行部分
。 - C语言的语句都是以
分号
结尾。
2.各种运算符、优先级和结合性
同优先级运算符依据结合性进行运算
优先级一般的人都能下图中看懂,但是结合性的话还是得有必须说明一下。
我们以赋值运算符为例:
我们可以从图中看成赋值运算符的结合顺序为从右往左。
例:
int a,b=1,c=2;
a=b=c;
然后求a的值,我们可以根据结合顺序从右往左,先把c赋值给b,然后把b赋值给a,所以a=2
,错误思想如果你从左往右看的话a=1(这种是错误的)。
下面这幅图有一个错误的地方,因为来自百度,我也不好修改:
错误地方:三目运算符的结合性是自右向左
的。
需要注意的知识点
- 5/2=2;5/2.0=2.5
- int p,i=2; p=++i+(++i);//p=8,i=4,因为++i的结合性是从右往左,而且++i的优先级高于+号,所以把i=4,然后4+4=8;
- a=35,a4,a+5;其中a=15,但是逗号表达式的值为20。求解思路:因为=赋值运算符的优先级高于,逗号运算符;所以先把3*5的值赋值给a,但是,号运算符的结合性是从左往右进行,所以逗号表达式的值为20。
- 当表达式中
存在有符号类型和无符号类型
时,按照左图中算术运算数据类型转换规则,所有的操作数都自动转换为无符号类型
。
例如:
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/
补充知识
- 使用
%p
读取地址数据。 - 其中L是有符号长整型,u是无符号整型,。在BC下,L占4字节,u占2字节;在V下,L和u都占4字节。
- %g或者%G:按照%f或%e中输出宽度比较短的一种格式输出。
2.有符号整数的输出
3.无符号整数的输出
4.实数的输出
5.字符和字符串的输出
6.辅助格式控制符小结
printf函数需要注意的地方
1.格式转换符中,除了X、E、G以外,其它均为小写。
2.printf函数不会进行不同数据类型之间的自动转换。例:float a=0.55,但以%d的形式输出时。
2.scanf 函数
3.字符数据的非格式化输入和输出
-
getchar:从键盘读取一个字符,以
回车键结束
,回显
,函数原型所在头文件stdio.h
。 -
putchar:在显示器上
输出字符c
。
函数int putchar(int c); -
getc:从流文件stream中读取一个字符信息,以
回车键结束
,回显
,函数原型所在头文件stdio.h
。
说明:该函数带有一个函数stream,它是一文件指针,当流文件是stdin时,getc函数的功能与getchar函数的功能完全相同。也就是说,getc(stdin)与getchar是等价的。 -
putc:将字符c输出道流文件stream。
函数:int putc(int c,FILE *stream) -
getche:与getchar的功能基本相同,
输入字符后就结束
,回显
,函数原型所在头文件conio.h
。 -
getch:接受一字符输入,
输入字符后就结束
,不回显
,函数原型所在头文件conio.h
。 -
puts:将
字符串
string的所有字符输出到屏幕上,自动回车
,函数原型所在头文件stdio.h
。
函数:int puts(char *string); -
fflush:清楚键盘缓冲区,函数原型所在头文件
stdio.h
。
其中回显的意思就是:
你从键盘输入的字符是否回显示出来
。
如下图:
我从键盘输入的是a和b,但是没有显示我输入b,因为getch不回显。
5.程序的控制结构及应用
程序=数据结构+算法
所以学好数据结构和算法很重要
1.C程序中语句的分类
- 表达式语句:表达式加上分号组成。
- 函数调用语句:由函数名、实际参数加上分号组成。
- 空语句:只有分号。
- 复合语句:用{…}括起来的一组语句。
- 控制语句:用来实现一定的控制功能的语句。例如:分支(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语句注意事项
- switch方面的"表达式",可以是int,char和枚举型中的一种,但
不可为浮点型
。 - case后面语句组可加{}也可以不加{},但一般不加{}。
- 每个case后面"常量表达式"的值,
必须各不相同
,否则会出现相互矛盾的现象。 - 每个case后面必须是"常量表达式",表达式中不能包含变量。
- case后面的"常量表达式"仅起到语句标号作用,并不进行条件判断。系统一旦找到入口标号,就从词标号开始执行,不再进行标号判断,所以
必须加上break语句
,以便结束switch语句。 - 多个case子句,可共用同一语句,不加break的时候。
- case子句和default子句如果都
带有break子句
,那么它们之间顺序的变化不会影响
switch语句的功能。 - case子句和default子句如果有的带有break子句,而有的
没有break子句
,那么它们之间顺序的变化可能会影响输出
的结果。 - switch语句可以
嵌套
。
4.while语句
while语句的注意事项 :
- 先判断
表达式
,再执行循环体
。 - while后面的
括号不能省
。 - while后面的表达式可以是
任意
类型的表达式,但一般是条件表达式或逻辑表达式。 - 表达式的值是循环的控制条件。
- 语句部分称为循环体。
- 如果while后的表达式的值一开始就为假,循环体将一次也不执行。
- 循环体的语句可为任意类型的C语句。
- 当表达式为
假
,或者遇到break、return或goto
,则退出while循环。 - 循环控制变化必须初始化,而且要在while语句的某处改变循环控制变量,否则极易构成死循环。
- 允许while语句嵌套。
5.do_while语句
do_while语句的注意事项:
- 先执行
循环体
,再判断表达式
。 - while后面的
括号
不能省。 - while后面的
分号
不能省。 - while后面的表达式可以是任意类型的表达式,但一般是条件表达式或逻辑表达式。
- 表达式的值是循环的控制条件。
- 语句部分称为循环体。
- 如果do_while后的表达式的值一开始就为假,循环体还是要执行
一次
。 - 循环体中的语句可为任意类型的C语句。
- 循环控制变化必须初始化,而且要在do_while语句的某处改变循环控制变量,否则极易构成死循环。
- do-while语句也可以组成多重循环,而且也可以和while语句相互嵌套。
6.for语句
for语句使用的注意事项:
- for后面的
括号
不能省。 - 表达式1:可以为任意表达式,一般为赋值表达式,给控制变量赋值。
- 表达式2:可以为任意表达式,一般为关系表达式或逻辑表达式,循环表达式。
- 表达式3:可以为任意表达式,一般为赋值表达式,给控制变量增量或减量。
- 表达式之间用
分号
隔开。 - 语句部分称为循环体。
- 表达式1、表达式2、和表达式3都是任选项,可以省掉其中的
一个、两个或全部
,但其用于间隔的分号是一个也不能省的。 - 表达式2如果为空则相当于表达式2的值为真,容易造成死循环。
- 可以嵌套。
7.break和continue、goto语句和exit函数
1.break语句
功能:在循环语句和switch语句中,终止并跳出循环体或开关体
。
说明:
- break不能用于循环语句和switch语句之外的任何其它语句之中。
- break只能终止并跳出最近一层的结构
2.continue语句
功能:结束本次循环,跳过循环体中尚未执行的语句
,进行下一次是否执行循环体的判断。
说明:
- 仅用于循环语句中。
- 在嵌套循环的情况下,continue语句只对包含它的最内层的循环体语句起作用。
3.goto语句
一般形式:
作用:goto语句的作用是在不需要任何条件
的情况下直接使程序跳转到该语句标号所标识的语句去执行。
说明:
语句标号
是按标识符规定书写的符号,放在某一语句行的前面,标号后加冒号(:)。语句标号起标识语句的作用,与goto 语句配合使用。- goto语句可与条件语句配合使用来实现条件转移,构成循环。
- 在嵌套循环的情况下,利用goto语句可以
直接从最内层的循环体跳出最外层的循环体
。
4.exit(int status)函数
功能:终止整个程序的执行
,强制返回操作系统。
说明:参数status为int型,status的值传给调用进程(一般为操作系统)。按照惯例,当status的值为0或为宏常量EXIT_SUCCESS时,表示程序正常退出;当status的值为非0或为宏常量EXIT_FAILURE时,表示程序出现某种错误后退出。
函数使用:
exit(-1);
6.字符串与数组,字符串常用函数
1.数组
1.定义说明
- 数组定义时,必须指定数组的大小,数组大小必须是
整型常量表达式
,不能是变量或者变量表达式,下标可以是整型常量、整型变量或整型表达式。 - 数组定义后,系统将给其分配一定大小的内存单元,其所占内存单元的大小与数组元素的类型和数组的长度有关;
数组所占内存单元的字节数 = 数组大小 × sizeof(数组元素类型)
。 - 数组中每个数组元素的
类型均相同
,它们占用内存中连续的存储单元,其中第一个数组元素的地址是整个数组所占内存块的低地址,也是数组所占内存块的首地址,最后一个数组元素的地址是整个数组所占内存块的高地址(末地址);下标的最小值是0
,最大值则是数组大小减1
。 - 只能
逐个引用
数组元素,不能一次引用整个数组。 - 数组引用要注意越界问题。
- 数组必须
先定义,后使用
。
2.一维数组的初始化赋值
- “=”后面的表达式列表一定要用
{ }括起来
,被括起来的表达式列表被称为初值列表,表达式之间用“,”分隔
; - 表达式的个数不能超过数组变量的大小;
- 表达式1是第1个数组元素的值,表达式2是第2个数组元素的值,依此类推。
- 如果表达式的个数小于数组的大小,则
未指定
值的数组元素被赋值为0
。 - 当对全部数组元素赋初值时,可以
省略数组变量的大小
,此时数组变量的实际大小就是初值列表中表达式的个数
。 - 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,5sizeof(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必须足够大
- 拷贝时’\0’一同拷贝
- 不能使用赋值语句为一个字符数组赋值
例:
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.地址传递方式
方式:函数调用时,将数据的存储地址
作为参数传递给形参。
特点:
- 形参与实参占用
同样
的存储单元。 双向
传递- 实参和形参必须是
地址
常量或变量。
可以用数组名和指针变量
作为参数的地址传递。
用数组名作为函数参数时主要事项:
- 形参数组和实参数组的类型必须一致。
- 形参数组和实参数组长度可以不相同,因为在调用时只传送首地址而不检查形参数组的长度。
- 多维数组也可以作为函数的参数。
8.变量的作用域和生存期
作用域和生存期是从空间和时间
的角度来体现变量的特性。
1.变量的作用域和生存期
按其作用域范围可分为两种:局部变量和全局变量
。
变量从被生成到被撤消的这段时间。实际上就是变量占用内存的时间。
按其生存期可分为两种:即动态变量和静态变量
2.局部变量作用域和生存期
定义:在函数内作定义说明的变量,也称为内部变量
。
作用域:仅限于函数内,离谱函数后不可再引用。
生存期:从函数被调用的时刻到函数返回调用处的时刻。
说明:
- 主函数main()中定义的变量也是局部变量,它只能在主函数中使用,其他函数不能使用。
形参变量属于被调用函数的局部变量
;实参变量则属于全局变量或调用函数的局部变量。- 允许在不同函数中使用相同的变量名。
在复合语句中定义的变量也是局部变量
;其作用域只在复合语句范围内。其生存期是从复合语句被执行的时刻到复合语句执行完毕的时刻。
3.全局变量作用和生存期
作用:在函数外部作定义说明的变量,也称为外部变量
。它不属于哪一个函数,而属于一个源程序文件。
作用域:从定义变量的位置开始到本源文件结束
,及有extern说明的其它源文件。
生存期:与程序相同。即从程序开始执行到程序终止的这段时间内,全局变量都有效。
说明:
- 应尽量少使用全局变量。
(1)全局变量在程序圈出执行过程中始终占用存储单元。
(2)降低了函数的独立性、通用性、可靠性及可移植性。
(3)降低程序清晰性,容易出错。 - 若外部变量与局部变量同名,则外部变量被屏蔽。要引用全局变量,则必须在变量名前家上两个冒号
"::"
。 全局变量定义
必须在所有的函数之外,且只能定义一次,并可赋初始值。全局变量说明可定义多次,但是全局变量定义只能定义一次。- 一般使用extern来对
全局变量说明
,可扩展全局变量的作用域。当extern定义时,赋了初值不是全局变量说明,而是全局变量定义。
9.变量的存储类型
1.存储类型的说明和分类
概述:变量是对程序中数据的存储空间的抽象。
变量的属性:
数据类型:变量所持有的数据的性质,规定了它们的取值范围和可参与的运算。
存储类型:规定了变量占用内存空间的方式,也称为存储方式。分为静态存储和动态存储
两种。
- 静态存储类型的变量是指在程序运行期间由系统分配固定的内存单元,并一直保持不变,直至
整个程序结束,内存空间才被释放
。前面所介绍的全局变量
即属于此类存储方式。 - 动态存储类型的变量:
用来存放变量以及进行函数调用时的现场信息和函数返回地址
等,在这个区域存储的变量称之为动态变量,如形参变量、函数体内部定义的动态局部变量
。对于存放于动态存储区的变量,是在函数调用开始时才分配动态存储空间,函数运行结束时释放这些空间。
2.4种变量的存储类型说明
1.自动变量(auto型变量)
说明:
存储类型说明符auto可以省略。
自动变量只能在函数内或复合语句种定义
,它属于局部变量。
2.外部变量(extern型变量)
外部变量和全局变量是对同一类变量的两种不同角度的提法。全局变是是从它的作用域提出的,外部变量从它的存储方式
提出的,表示了它的生存期。它属于静态存储类型
。
3.静态变量(static型变量)
静态变量和静态存储变量的异同:
- 静态变量的类型说明符是
static
。 - 静态变量
属于
静态存储类型。 - 静态存储类型的变量不一定就是静态变量。例如
外部变量虽属于静态存储类型但不一定是静态变量
,必须由static
加以定义后才能成为静态外部变量,或称静态全局变量
。 自动变量
可以用static
定义它为静态自动变量,或称为静态局部变量
,从而成为静态存储类型。
静态局部变量与自动变量之比较:
- 静态局部变量与自动变量均为
局部变量
。 - 静态局部变量
生存期长
,为整个源程序
。自动变量生存期短。 - 静态局部变量的生存期虽然为整个源程序,但是其
作用域
仍与自动变量相同
。 - 静态局部变量若在定义时未赋值初值,则
系统自动赋初值0
;但是自动变量不会赋初值0,而是赋值为随机数
。 - 静态局部变量赋初值只
一次
,而自动变量赋初值可能多次
。
静态全局变量:
全局变量(外部变量)的说明之前再冠以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.指针变量的存取方法
直接访问:按变量名来存取变量值。
间接访问:通过存放变量地址的变量去访问变量;指针属于间接访问。
指针变量的定义的注意事项:
- int *p1,*p2;与int *p1,p2;
- 指针变量名是p1,p2,不是*p1,*p2。
- 指针变量只能指向定义时所规定类型的变量
- 指针变量定义后,变量值不确定,应用前必须先赋值。
- 指针变量的初始化赋值是赋值
初始地址值
。 - 赋值变量的类型必须与指针变量类型相同。
- 不允许直接把一个数赋值给指针变量。
- 当在赋值语句中,指针变量不需要加*。例:p=&a;
- 不能用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];
对指向二维数组的行指针变量进行赋值一般形式为:
二维数组名+整型常数n
。如: p=a+1;&二维数组名[整型常量n]
。如:p=&a[1]。- 如:
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];
- str由若干元素组成,每个元素放一个字符;而cp中存放字符串首地址
- char str[20]; str=“I love China!”; (×)
char *cp; cp=“I love China!”; (√) - str是地址常量;cp是地址变量
- cp接受键入字符串时,必须
先开辟存储空间
。
字符指针变量使用注意事项:
- 当字符指针指向字符串时,除了可以被赋值之外,与包含字符串的字符数组没有什么区别,
字符指针可以被字符串的函数使用
;例如:char *pstr;pstr=“12345”;strlen(pstr);。 - 如果一个指针
没有指向一个有效内存
就被引用,则被称为“野指针
”操作或空指针
赋值。野指针操作尽管编译时不会出错,但很容易引起程序运行时表现异常,甚至导致系统崩溃。
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的使用的注意事项:
- malloc前面必须要
加上一个指针类型转换符
,如前面的(int *)。因为malloc的返回值是空类型的指针,一般应与右边的指针变量类型一致。 - malloc所带的一个参数是指需分配的内存单元字节数,尽管可以直接用数字来表示,但一般写成如下形式:
分配数量*sizeof(内存单元类型符)
。 - malloc可能返回NULL,表示分配内存失败,因此一定要
检查分配的内存指针是否为空
,如果是空指针,则不能引用这个指针,否则会造成系统崩溃。所以在动态内存分配的语句的后面一般紧跟一条if语句以判断分配是否成功。
2.calloc函数
函数calloc()用于给若干个同一类型的数据项分配连续的存储空间,其中每个数据项的长度单位为字节。通过调用函数calloc()所分配的存储单元,系统将其自动置初值0
。
说明:
num
表示向系统申请的内存空间的数量
,size
表示申请的每个内存空间的字节数
。- 如果分配内存失败,将返回空指针(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]:
- 指针数组名是二级指针常量。
p=q;p+i=q[i]的地址
。- 指针数组作形参时,
int *q[ ]与int **q完全等价
;但作为变量定义两者不同。 - 系统
只给p分配能保存一个指针值的内存区
;而给q分配10块内存区,每块可保存一个指针值。
2.指针作为函数的参数
参数传递方式:传值调用和传址调用
。
传值调用:将实参数值
传递给形参。实参和形参占用各自的内存单元,互不干扰,函数中对形参值得改变不会改变实参的值,属于单向数据传递方式
。
传址调用:将实参的地址
传递给形参。形参和实参占用同样的内存单元,对形参值得改变也会改变实参的值,属于双向数据传递方式。
3.指针型参数常态的使用
两种含义:
- 指
形参指针本身是常态(int *const p)
;则*p=5;正确,但是int a[10];p=a;将引起错误;只能修改值
。 - 指形参
指针所指向的内存单元是常态(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
等。
格式:
- ”#“开头
- 占单独书写行
- 语句尾不加分号
2.宏定义
宏定义分为两种:不带参数的宏定义和带参数的宏定义
。
1.不带参数的宏定义:
功能:用指定标识符(宏名)代替字符序列(宏体)
宏替换时仅仅是将源程序中与宏名相同的标识符替换成宏的内容文本,并不对宏的内容文本做任何处理。
宏定义的注意事项:
-
C程序员通常用写字母来定义宏名,以便与变量名区别。
-
宏定义的位置任意,但一般放在函数外面。
-
宏定义时,如果单词串太长,需要写多行,可以在行尾使用反斜线”\“续行符。
-
宏名的作用域是从#define定义之后直到该宏定义所在文件结束。
-
#undef可终止宏名作用域。
-
宏定义可以嵌套定义,但不能递归定义。
-
程序中字符串常量即双引号中的字符,不作为宏进行宏替换操作。
-
宏定义一般以
换行结束
,不要用分号结束,以免引起不必要的错误。 -
宏可以被
重复定义
。 -
在定义宏时,如果宏是一个表达式,那么一定要将这个表达式用()括起来,否则可能会引起非预期的结果。
2.带参数的宏定义
宏展开:形参用实参换,其它字符保留。
宏体及各形参外一般应加括号()
带参的宏与函数区别:
3.文件包含
功能:一个源文件可将另一个源文件的内容全部包含气进来。
一般形式:
例如:
#include “head.h”
4.条件编译
根据一定的条件去编译源文件的不同部分,这就是条件编译。
条件编译与分支语句二者之间的差别:
1.条件编译是将满足
编译条件的程序代码进行编译生成目标代码
,不满足
编译条件的程序代码将不进行编译
;而分支语句则是不管满足条件的代码,还是不满足条件的代码,都要编译生成目标代码
(包括分支语句本身),所以如果用条件语句来代替条件编译命令,程序的目标代码将变长 。
条件编译命令
可以放在所有函数的外部
,也可以放在某函数的内部
;但分支语句
只能出现在某函数内部
。