return 0;
}
运行结果: A
press anykey to continue
字符型数据的存储空间和值的范围
———————————————————————————————————————
| 类型 | 字节数 | 取值范围 |
———————————————————————————————————————
| signed char 有符号字符型 | 1 | -128~127,即2^7~2^7-1 |
———————————————————————————————————————
| unsigned char 无符号字符型 | 1 | 0~255,即0~2^8-1 |
———————————————————————————————————————
说明:在使用有符号字符型变量时,允许存储的值为-127~127,单字符的代码不可能为负值,所以在存储字符时实际上只用到
0~127这一部分,其第1位都是0。
如果将一个负整数赋给有符号字符型变量是合法的,但它不代表一个字符,而作为一字节整型变量存储负整数。
附注:前面已介绍,127个基本字符用7个而仅为存储,如果系统只提供127个字符,那么就将char型变量的第一个二进位设为0,
用后面的7位存放0到127个字符的代码。在这种情况下,系统提供的char类型详单与 signed char。但是在实际应用中,
往往觉得127个字符不够用,希望能多提供一些可用的字符。根据此需要,有的系统提供了扩展的字符集,把可用的字符由
127个扩展为255个即扩大了一倍。就是把本来不用的第一位用起来,把char变量改为 unsigned char,即第一位并不固定
设为0,而是把8位都用来存放字符代码。这样,可以存放255个字符代码,但它并不适用于所有的系统。
—浮点型数据
浮点型数据是用来表示具有小数点的实数的。
float型 单精度浮点型
float占4个字节,能得到6位有效数字。
double型 双精度浮点型
double占8个字节,能得到15位有效数字。
(在C语言中进行浮点数的算术运算时,将float型数据自动转换成double型,再进行运算)
long double型 长双精度浮点型
long double占16个字节,能得到19位有效数字。
—怎样确定常量的类型
整型常量
在一个整数的末尾加大写字母L或小写字母l,表示它是常整型。
浮点型常量
从编译系统把浮点型常量都按双精度处理,分配8个字节,例如:
float a = 3.14159;
在进行编译时,对float变量分配4个字节,但对于浮点型常量3.14159,按双精度处理,分配8个字节。
以上程序会损失精度,但是可以用以下方法来强制指定常量的类型:
float a = 3.14159f; //把此3.14159按单精度浮点型常量处理,编译时不出现警告。
long double a = 1.23L; //把此1.23作为long double处理
—运算符和表达式
算术运算符
+ - * / %
/ 两个实数相除的结果是双精度实数,两个整数相除的结果是整数,5/3=1,舍去小数部分,
但是如果除数或被除数中有一个为负值,则舍入的方向是不固定的。
% %运算符要求参加运算的对象(即操作数)为整数,结果也是整数。如8%3 = 2。
除 % 以外的运算符的操作数可以是任何算术类型。
自增自减
++i;--i; (在使用i之前,先使i的值+1或-1)
i++;i--; (在使用i之后,再使i的值+1或-1)
注意:自增运算符和自减运算符只能用于变量,而不能用于常量或表达式。
自增运算符常用于循环语句中,使循环变量自动+1;也用于指针变量,使指针指向下一个地址。
算术表达式和运算符的优先级与结合性
自右至左,即右结合性。
不同类型数据间的混合运算
整型、实型、字符型数据之间可以进行混合运算:
1、+ - * / 运算的两个数中有一个数位float 或 double 型,结果都是double型,因为系统将所有的float型数据都先转换为
double 型再进行运算。
2、如果 int 型和float或double型进行运算,先把int型和float型转换为double型,再进行运算,结果为double型。
3、字符 char型数据与整型数据进行运算,就是把字符的ASCII码与整型数据进行运算,如果字符型数据与实行数据
进行运算,则将字符的ASCII代码转换为double型再进行运算。
一个字符型数据既可以以字符形式输出,也可以以整数形式(十进制)输出。
强制类型转换运算符
可以利用强制类型转换运算符将一个表达式转换成所需类型。例如:
(double) a (将a转换成double类型)
(int)(x+y) (将 x+y的值转换成int型)
(float)(5%3) (将5%3的值转换成float型)
其一般形式为 (类型名)(表达式)
需要说明的是,在强制类型转换时,得到一个所需类型的中间数据,而原来变量的类型未发生变化。例如:
a = (int)x
如果定义x为float型,a为整型变量,进行强制类型运算(int)a之后得到一个int类型的临时值,它的值等于x 的整数部分,
把它赋给a,注意x的值和类型都未发生变化,仍为float类型,该临时值在赋值后就不存在了。另外,在函数调用时,有时
为了使实参与形参类型一致,可以使用强制类型转换运算符得到一个所需类型的参数。
C运算符
(1)算术运算符 (+ - * / % ++ --)
(2)关系运算符 (> < == >= <= !=)
(3)逻辑运算符 (&& || ! )
(4)位运算符 (<< >> ~ | ^ &)
(5)赋值运算符 (= 及其扩展赋值运算符)
(6)条件运算符 ( ? :)
(7)逗号运算符 ( , )
(8)指针运算符 ( * )
(9)求字节数运算符 (sizeof)
(10)强制类型转换运算符 ( (类型) )
(11)成员运算符 ( . - >)
(12)下标运算符 ( [ ] )
(13)其他 (如函数调用运算符())
— C 语句
控制语句
1、if()… else 条件语句
2、for()… 循环语句
3、while()… 循环语句
4、do…while() 循环语句
5、continue 结束本次循环语句
6、break 终止执行switch语句或循环语句
7、switch 多分支选择语句
8、return 函数返回语句
9、goto 转向语句,在结构化程序中基本不用goto语句
复合的赋值运算符
+= -=
赋值运算符左侧应该是一个可修改的“左值”,左值的意思是他可以出现在赋值运算符的左侧,它的值是可以改变的。
并不是任何形式的数据都可以作为左值,变量可以作为左值,而算术表达式 a+b 就不能作为左值,
常量也不能作为左值,因为常量不能被赋值。能出现在赋值运算符右侧的表达式称为“右值”,
显然左值也可以出现在赋值运算符的右侧,因而凡是左值都可以作为右值。
赋值过程中的类型转换
如果赋值运算符两侧的类型不一致,但都是算数类型时,在赋值时要进行类型转换,类型换换是由系统自动进行的,
转换的规则是:
1、将浮点型数据(包括单、双精度)赋给整型变量时,先对浮点数取整,即舍弃小数部分,然后赋予整型变量。
如果 i 为整型变量,执行"i=3.56"的结果是使i的值为3,以整数形式存储在整型变量中。
2、将整型数据赋给单、双精度时,数值不变,但以浮点数形式存储到变量中。如果有float变量,执行“f=23;”。
先将整数23转换成实数23.0,再按指数形式存储在变量f中,如果23赋给double型变量 d ,即执行“d = 23;”,
则将整数23转换成双精度实数23.0,然后以双精度浮点数形式存储到变量d中。
3、将一个double 型数据赋给float变量时,先将双精度转换为单精度,即只取6~7位有效数字,存储到float变量的4个
字节中。应注意双精度数值的大小不能超出float型变量的数值范围。
4、字符型数据赋给整型变量时,将字符的ASCII码赋给整形变量。
5、将一个占字节多的整型数据赋给一个占字节少的整型变量或字符变量时,只将低字节的数据赋值,即发生截断。
赋值语句和赋值表达式
在if的条件中可以包含赋值表达式,但不能包含赋值语句。
赋值表达式:a = 5
赋值语句:a = 5;
表达式没有分号,而赋值语句有分号。
变量赋初值
一般变量初始化不是在编译阶段完成的(只有在静态存储变量和外部变量的初始化是在编译阶段完成的),而是在程序
运行时执行本函数时赋予初值的,相当于执行一个赋值语句。
—数据的输入输出
C语言本身不提供输入输出的语句,输入和输出操作是由从标准函数库中的函数来实现的。
在调用标准输入输出库函数时,文件开头应该有以下预处理指令。
# include
//系统定义函数,是标准方式。
或
# include "stdio.h" //用户定义函数
用printf函数输出数据
格式字符
1、%d格式符:输出一个有符号的十进制整数,若输出long数据,在d前面加字母l,即%ld。
2、%c格式符:用来输出一个字符。(ASCII码中0~127是标准的,128~255是扩展的,因为字符变量无符号)
一个整数,如果在0~127范围中,也可以用%c输出,因为字符变量实际上是一个字节的整型变量。
如果整数较大,则把它的最后一个字节的信息以字符形式输出。
3、%s格式符:用来输出一个字符串。
4、%f格式符:用来输出实数(包括单、双精度、长双精度),以小数形式输出,有以下几种用法:
①基本型,用%f:
不指定输出数据的长度,由系统根据数据的实际情况决定数据所占的列数。
系统处理的方法一般是:实数中的整数部分全部输出,小数部分输出6位。
②制定数据宽度和小数位数,用%m.nf:
m表示数据宽度,n表示小数位数,m包括符号位。
③输出的数据向左对齐,用%-m.nf:
输出默认右对齐,- 表示左对齐。
5、%e格式符:用格式声明%e 指定以指数形式输出实数。如果不指定输出数据所占的宽度和数字部分的小数位数,
系统会自动给出数字部分的小数位数为6位,指数部分占5列(如e+002)。数值按标准化指数形式
输出(即小数点前必须有而且只有1位非零数字)。例如:
1.234560e+002
其它格式符:
1、i 格式符:作用与d格式符相同。
2、o 格式符:以八进制整数形式输出。
3、x 格式符:以十六进制数形式输出。
4、u 格式符:用来输出无符号(unsigned)型数据,以十进制整数形式输出。
5、g格式符:用来输出浮点数,系统自动选择 f 格式或者 e 格式输出,选择其中长度较短的格式,不输出无意义的0。
用scanf函数输入数据
scanf函数所用的格式字符和附加字符用法与printf相似。
使用scanf函数时应注意的问题
1、scanf函数中的“格式控制”后面应当是变量地址,而不是变量名,要使用&运算符。
2、如果在“格式控制字符串”中除了格式声明以外还有其他字符,则在输入数据时在对应的位置上应输入与这些字符
相同的字符。
3、在用“%c ”格式声明输入字符时,空格字符和“转义字符”中的字符都作为有效字符输入,因为%c是字符格式,
所以输入的每个字符都会被存储到相应的变量当中。
4、在输入数值数据时,如输入空格,回车,tab键或遇非法输入(不属于数值的字符),则认为该数据结束。
字符数据的输入输出
putchar() 输出一个字符
putchar函数即可以输出能在显示器上显示的字符,也可以输出屏幕控制字符,如put('\n')
\\ 整型数据与字符是通用的,可以再putchar()中输入该字符的ASCII码。
getchar() 输入一个字符
在键盘输入信息时,并不是在键盘上敲一个字符,该字符就立即送到计算机中去的,这些字符先暂存在键盘的缓冲
器中,只有按了Enter键才把这些字符一起输入到计算机中,然后按先后顺序分别赋给相应的变量。
执行getchar()可以从输入设备获取一个可显示的字符,也可以获取一个控制字符。
用getchar()函数得到的字符可以赋给一个字符变量,也可以作为表达式的一部分putchar(getchar())。
puts() 输出字符串
gets() 输入字符串
第4章 选择结构程序设计
—关系运算符与表达式
运算符优先等级
!(非) > 算术 > 关系 > 逻辑 > 条件 > 赋值
逻辑运算符与表达式
实际上,逻辑运算符两侧的运算对象不但可以是0和1,或者0和非0的正数,也可以是字符型、浮点型、枚举型或指针型的
纯量数据,系统最终以0和非0来判断他们属于真或假。
逻辑型变量(布尔型变量)C语言中没有bool型变量,C++中有
—条件运算符和条件表达式
( ? : )是三目运算符,要求有三个操作对象,它是C语言中的唯一一个三目运算符。
int a = 1,b = 2,c = 3;
printf("%d",a>b?a:(a
—switch语句
switch后面括号内的”表达式“,其值的类型只能是整数类型或者字符型。
第5章 循环结构程序设计
逗号表达式从左向右,赋值表达式从右向左。
在循环结构中定义的变量的有效范围仅限循环结构中,在循环外不能使用此变量。
break语句只能用于循环语句和switch语句中,而不能单独使用,如果是双重循环,在内循环中有break语句,则break仅中止内循环。
第6章 利用数组处理批量数据
—怎样定义和引用一维数组
如果在被调用的函数(不包括主函数)中定义数组,其长度可以是变量或非常量表达式,如:
void fun(int n)
{
int a [2 * n] // 合法,n的值从实参传来。
}
在调用fun函数时,形参n从实参得到值,这种情况被称为“可变长数组”,允许每次调用fun函数时,n有不同的值。
但是在执行函数时,n的值是不变的,数组长度是固定的,如果定义数组为静态存储方式,则不能用“可变长数组”,如:
static a [2 * n] // 不合法,a数组指定为静态static存储方式。
—一维数组的初始化
1、在定义数组时对全部数组元素赋予初值,如:
int a[5] = {1,2,3,4,5};
2、可以只给数组中的一部分元素赋值,如:
int a[5] = {1,2,3};
系统自动给后两个元素赋初值为0。
3、如果想使数组中全部元素值为0,可以写成:
int a[5] = {0};
或
int a[5] = {0,0,0,0,0}
4、在对全部数组元素赋初值时,由于数据的个数已确定,因此可以不指定数组长度,如:
int a[] = {1,2,3,4,5};
说明:如果在定义数值型数组时,指定了数组的长度并未对之初始化,凡未被初始化列表指定初始化的数据元素,
系统会自动把它们初始化为0(如果是字符型数组,则初始化为'\0',如果是指针型数组,则初始化为NULL,即空指针)。
—怎样定义和引用二维数组
用矩阵形式表示二维数组,是逻辑上的概念,能形象的表示出行列的关系,而在内存中每个元素是连续存放的,不是二位的,
而是线性的。
—二维数组的初始化
1、分行给二维数组赋初值,如:
int a[3][4] = { {1,2,3,4}{5,6,7,8}{9,10,11,12}};
2、int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
3、可以对部分元素赋初值,如:
int a[3][4] = { {1},{5},{9}};
它的作用是只对各行第一列(即序号为0的列)的元素赋初值,其余元素自动为0,也可以对各行中的某一元素赋初值,如:
int a[3][4] = { {1},{0,6},{0,0,11}};
初始化后的数组元素如下:
1 0 0 0
0 6 0 0
0 0 11 0
4、如果对全部元素都赋初值(即提供全部数据),则定义数组时对第1维数组的长度可以不指定,但第2维的长度不能省,如:
int a[][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
系统会根据数据总个数和第2维的长度算出第1维的长度。
—字符串
使用字符串处理函数 # include
1、puts()函数——输出字符串的函数
用puts()函数输出的字符串中可以包含转义字符。
2、gets()函数——输入字符串的函数
一般利用gets函数的目的是向字符数组输入一个字符串,而不关心其函数值。
注意:用puts和gets函数只能输出或输入一个字符串。
3、strcat()函数——字符串连接函数
其一般形式为:
strcat(字符数组1,字符数组2)
strcat是STRingCATenate(字符串连接)的缩写其作用是把两个字符串连接起来,把字符串2接到字符串1的后面,结果
放在字符数组1中,函数调用后得到一个函数值——字符数组1的地址。
说明:
1、字符数组1必须足够大,能容纳字符数组2.。
2、连接前两个字符串后面都有'\0',连接时将字符串1后面的'\0'取消,只在新串最后保留'\0'。
4、strcpy()和strncpy()函数——字符串复值函数
其一般形式为
strcpy(字符数组1,字符串2)
strcpy是STRingCoPY的缩写(字符串复制),它表示将字符串2复制到字符数组1中去。
说明:
1、字符数组1必须足够大,能容纳字符串2。
2、“字符数组1”必须写成数组名形式(str1),“字符串2”可以是字符数组名也可以是一个字符串常量。
3、复制时将str2中的字符串和其后的'\0'一起复制到字符数组1中,取代字符数组1中的前n个字符,未复制的不一定是
'\0',而是str1中原有的内容。
4、不能用赋值语句直接赋值。
5、可以用strncpy函数将字符串2中前面n个字符复制到字符数组1中去,但复制的字符个数不应多于str1中原有的字符,不
包括'\0'。如:
strcpy(str1,str2,5);
5、strcmp()函数——字符串比较函数
其一般形式为:
strcmp(字符串1,字符串2)
strcmp是STRing CoMPare(字符串比较)的缩写。它的作用是比较字符串1和字符串2。
说明:
字符串比较的规则是:将两个字符串自左向右逐个字符比较(按ASCII码值大小比较),直到出现不同的字符或'\0'为止。
1、如全部字符相同,则认为两个字符串相等。
2、若出现不同的字符,则以第1对不相同的字符的比较结果为准。
比较的结果由函数值带回。
1、如果字符串1 = 字符串2,则函数值为0。
2、如果字符串1 > 字符串2,则函数值为一个正整数。
3、如果字符串1 < 字符串2,则函数值为一个负整数。
注意:对两个字符串比较,不能用以下形式:
if (str1 > str2)
printf("yes\n");
而只能用:
if (strcmp(str1 > str2) > 0)
printf("yes\n");
6、strlen()函数——测字符串长度
其一般形式为:
strlen(字符数组)
strlen是STRingLENgth(字符串长度)的缩写。它是测试字符串长度的函数,函数的值为字符串中的实际长度(不包括'\0')
char str[10] = "China";
printf("%d\n",strlen(str)); //或者strlen("China");
输出:5
7、strlwr()函数——转换为小写的函数
8、strupr()函数——转换为大写的函数
第7章 用函数实现模块化程序设计
—调用函数
如果函数值的类型和return语句中表达式的值不一致,则以函数类型为准。对数值型数据,可以自动进行转换,即函数类型决
定返回值的类型。
当函数类型被定义为void型时,函数体重不得出现return语句。
—函数的递归调用
在调用一个函数的过程中又出现直接或间接的调用该函数本身,称为函数的递归调用。如:
int fun(int x)
{
int x,y;
y = fun(x);
return z;
}
在调用函数fun的过程中,又要调用fun函数,这是直接调用本函数。
如果在调用函数f1的过程中要调用f2函数,而在调用f2函数过程中又要调用f1函数,就是间接调用本函数。
举个例子:有5个学生坐在一起,问第5个学生多少岁,他说比第4个学生大2岁,问第4个学生多少岁,他说比第3个学生大2岁,
问第3个学生多少岁,他说比第2个学生大2岁,问第2个学生,他说比第1个学生大2岁,第一个学生10岁,请问第5
个学生多大?
即:
age(5) = age(4) + 2;
age(4) = age(3) + 2;
age(3) = age(2) + 2;
age(2) = age(1) + 2;
age(1) = 10;
# include
int main (void)
{
int age(int n);
printf("NO.5,age:%d\n",age(5));
return 0;
}
int age(int n)
{
int c;
if (n == 1)
c = 10;
else
c = age(n-1) + 2;
return c;
}
当最后一次递归调用结束的时候,开始依次出栈,
出栈从最后那次调用开始,直到第一次调用结束。
用递归方法求 a!。
# include
int main (void)
{
int jiecheng(int n);
int x;
scanf("%d",&x);
printf("%d\n",jiecheng(x));
return 0;
}
int jiecheng(int a)
{
if (a<0)
printf("data error!\n");
else if (a == 1 || a == 0)
a = 1;
else
a = jiecheng(a-1) * a;
return a;
}
—数组作为函数参数
数组元素可以用作函数实参,不能用作形参,因为形参是在函数被调用时临时分配的存储单元,不可能为一个数组元素单独
分配存储单元(数组是一个整体,在内存中占连续的一段存储空间)。在用数组元素做函数实参时,把实参的值传给形参,
是“值传递”的方式。数据传递的方向是从实参传到形参,单向传递。
出了用数组元素作为函数参数外,还可以用数组名作为函数参数(包括实参和形参),应当注意的是:用数组元素作实参时,
向形参变量(数组名或指针变量)传递的是数组首元素的地址。
—局部变量和全局变量
定义变量有三种情况:
1、在函数的开头定义。
2、在函数内的复合语句定义。
3、在函数的外部定义。
局部变量
在一个函数内部定义的变量只在本函数范围内有效,也就是说只有在本函数内才能引用它们,在此函数意外是不能使用这些
变量的。在复合语句内定义的变量只在本复合语句范围内有效,在该复合语句以外是不能使用这些变量的,以上这些称为“局部
变量”。
全局变量
在函数内定义的变量是局部变量,而在函数外定义的变量称为外部变量,外部变量是全局变量(也称全程变量)。全局变量可以
为本文件中其他函数所共用。它的有效范围为从定义变量的位置开始到本源文件结束。
注意:在函数内定义的变量是局部变量,在函数外定义的变量是全局变量。
为了便与区别全局变量和局部变量,在C程序设计人员中有一个习惯(但非规定),将全局变量名的第一个字母用大写表示。
当局部变量和全局变量冲突时,局部变量有效,全局变量被屏蔽。
从作用域看:
全局变量具有全局作用域。全局变量只需在一个源文件中定义,就可以作用于所有的源文件。当然,其他不包括全局变量定义的
源文件需要用extern关键字再次声明这个全局变量。
—变量的存储方式和生存期
动态存储方式与静态存储方式
从变量的作用域的角度来观察,变量可以分为全局变量和局部变量。
还可以从另一个角度,即从变量值存在的时间(即生存期)来观察。有的变量在程序运行的整个过程都是存在的,而有的变量则
是在调用其所在的函数是才临时分配存储单元,而在函数调用结束后该存储单元马上就释放了,变量不存在了。也就是说,变量
的存储有两种不同的方式:静态存储方式和动态存储方式。静态存储方式是指在程序运行期间由系统分配固定的存储空间的方式,
而动态存储方式则是在程序运行期间根据需要进行动态的分配存储空间的方式。
先看一下内存中的供用户使用的存储空间的情况。这个存储空间可以分为3个部分:
1、程序区。
2、静态存储区。
3、动态存储区。
数据分别存放在静态存储区和动态存储区中。全局变量全部存放在静态存储区中,在开始执行时给全局变量分配存储区,程序执行
完毕就释放。在程序执行过程中它们占据固定的存储单元,而不是动态的进行分配和释放。
在动态存储区中存放以下数据:
1、函数形式参数。在调用函数时给形参分配存储空间。
2、函数定义的没有用的关键字static声明的变量,即自动变量。
3、函数调用时的现场保护和返回地址等。
对以上这些数据,在函数调用开始时分配动态存储空间,函数结束时释放这些空间。在程序执行过程中,这种分配和释放是动态的,
如果在一个程序中两次调用同一函数,而在此函数中定义了局部变量。在两次调用时分配给这些局部变量的存储空间的地址可能是
不相同的。
在C语言中,每一个变量和函数都有两个属性:数据类型和数据的存储类别。存储类别指的是数据在内存中存储的方式(如静态存
储和动态存储)。C语言的存储类别包括4种:自动的 auto(系统默认类别)、静态的 statis、寄存器的 register、外部的 extern。
自动变量 (auto自动变量):
函数中的局部变量,如果不专门声明为 static 静态存储类别,都是动态地分配存储空间的,数据存储在动态存储区中。函数中
的形参和在函数中定义的局部变量(包括在复合语句中定义的局部变量),都属于此类。在调用该函数时,系统会给这些变量
分配存储空间,在函数调用结束时就自动释放这些存储空间。因此这类局部变量称为自动变量。自动变量用关键字 auto作为
存储类别的声明。实际上,关键字 auto可以省略,不写auto 则隐含指为“自动存储类别”,它属于动态存储方式。程序中大多
数变量都属于自动变量。
静态局部变量(static局部变量)
有时希望函数中的局部变量的值在函数调用结束后不消失而继续保留原值,即其占用的存储单元不释放,在下一次再调用该
函数时,该变量已有值(就是上一次函数调用结束时的值)。这是就应该指定该局部变量为“静态局部变量”,用关键字 static
进行声明。
寄存器变量(register 变量)
一般情况下,变量(包括静态存储方式和动态存储方式)的值是存放在内存中的。当程序中用到哪一个变量的值时,由控制器
发出指令将内存中该变量的值送到运算器中。经过运算器进行运算,如果需要存数,再从运算器将该数据送到内存中。
如果有一些变量使用频繁(例如在一个函数中执行10000次循环,每次循环中都要引用某局部变量),则为存取变量的值要花
费不少时间。为提高执行效率,允许将局部变量的值放在CPU中的寄存器中,需要时直接从寄存器中取出参加运算,不必再
到内存中去存取,。由于对寄存器的存取速度远高于对内存的存取速度,因此这样做可以提高执行效率。这种变量叫做寄存
器变量。用关键字register 作声明即可。如:
register int f; //定义f为寄存器变量。
由于现在的计算机的速度越来越快,性能越来越高,优化的编译系统能够识别使用频繁的变量,从而自动的将这些变量存放
在寄存器中,而不需要程序设计者去指定。因此,现在实际上用register 声明变量的必要性不大,只需了了解即可。
全局变量(extern变量)
全局变量都是存放在静态存储区中的,因此它们的生存期是固定的,存在于程序的整个运行过程。一般来说,外部变量是在
函数的外部定义的全局变量,它的作用域是从变量的定义处开始,到本程序文件的末尾。在此作用域内,全局变量可以为程
序中各个函数所引用。但有时程序设计人员希望能扩展外部变量的作用域,有以下几种情况:
1、在一个文件内扩展外部变量的作用域。
如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件的结束。在定义点之前的函数不能引用该外
部变量。如果在定义点之前的函数需要引用该外部变量,则应该在饮用之前用关键字extern对该变量做“外部变量声
明”,表示把该外部变量的作用于扩展到此位置,有了此声明,就可以从声明处起,合法的使用该外部变量。
用extern 声明外部变量时,类型名可以写也可以省写。例如“extern int A,B,C;”也可以写成“extern A,B,C;”.因为它不
是定义变量,只需写出外部变量名即可。
2、将外部变量的作用域扩展到其他文件
在任意一个文件中定义外部变量A,而在灵异文件中用 extern 对A作“外部变量声明”,即“extern A;”
当文件和函数都有该变量时,extern的处理方式为:先找函数,再找文件,找不到按出错处理。
3、将外部变量的作用域限制在本文件中
有时在程序设计中希望某些外部变量只限于被本文件引用,而不能被其他文件引用,可以在定义外部变量时加一个
static 声明。例如:
file1.c file2.c
static int A; extern A;
A = A * 2; //出错
这种加上static声明、只能用于本文件的外部变量称为“静态外部变量”。
用static 声明一个变量的作用是:
1、对局部变量用static声明,把它分配在静态存储区,该变量在整个程序执行期间不释放,其所分配的空间始终存在。
2、对全局变量用static声明,则该变量的作用于只限于本文件模块
注意:
用auto、register、static声明变量时,是在定义变量的基础上加上这些关键字,而不能单独使用,编译时会被认为“重新
定义”。
存储类别小结
对一个数据的定义,需要指定两种属性:数据类型和存储类别,分别使用两个关键字:
static int a; //静态局部整型变量或静态外部整型变量
auto char c; //自动变量,在函数内定义(自动变量是自动创建自动释放,存在于栈;动态变量需要申请内存 malloc)
register int b; //寄存器变量,在函数内定义
此外,可以用extern声明已定义的外部变量:
extern b //将已定义的外部变量b的作用域扩展至此
下面从不同角度做归纳:
1、从作用域角度区分,有局部变量和全局变量。它们采用的存储类别如下:
按作用域角度区分:
(1)局部变量:自动变量,即动态局部变量(离开函数,值就消失)。
静态局部变量(离开函数,值仍保留)。
寄存器变量(离开函数,值就消失)。
(形式参数可以定义为自动变量或寄存器变量)
(2)全局变量:静态外部变量(只限本文件引用)。
外部变量(即非静态的外部变量,允许其他文件引用)。
2、从变量存在的时间(生存期)来区分,有动态存储和静态存储两种类型。静态存储是程序整个运行时间都存在,而动态
存储则是在调用函数时临时分配单元。
按变量的生存期区分:
(1)动态存储:自动变量(本函数有效)。
寄存器变量(本函数内有效)。
形式参数(本函数内有效)。
(2)静态存储:静态局部变量(函数内有效)。
静态外部变量(本文件内有效)。
外部变量(用extern声明后,其他文件可引用)。
3、从变量值存放的位置来区分:
(1)内存中静态存储区:静态局部变量
静态外部变量(函数外部静态变量)。
外部变量(可为其他文件引用)。
(2)内存中动态存储区:自动变量和形式参数。
(3)cpu中的寄存器:寄存器变量。
4、关于作用域和生存期的概念。
如果一个变量在某个文件或函数范围内是有效的,就称该范围为该变量的作用域,在此作用域内可以引用该变量,在专
业书中称变量在此作用域内“可见”。如果一个变量值在某一时刻是存在的,则认为这一时刻属于该变量的生存期,或称
该变量在此时刻“存在”。
———————————————————————————————————
| | 函数内 | 函数外 |
| 变量存储类别 ——————————————————————
| | 作用域 | 存在性 | 作用域 | 存在性 |
———————————————————————————————————
| 自动变量和寄存器变量 | √ | √ | × | × |
———————————————————————————————————
| 静态局部变量 | √ | √ | × | × |
———————————————————————————————————
| 静态外部变量 | √ | √ |√只限本文件| √ |
———————————————————————————————————
| 外部变量 | √ | √ | √ | √ |
———————————————————————————————————
5、static对局部变量和全局变量的作用不同,对局部变量来说,它使变量由动态存储方式改变为静态存储方式。而对全局
变量来说,它使变量局部化(局限于本文件),但仍为静态存储方式,从作用域角度来看,凡有static声明的,其作用
都是局限的,或者是局限于本函数内(静态局部变量)。或者局限于本文件内(静态外部变量)。
第8章 善于利用指针
如果p的初值为&a[0],则p+i和a+i就是数组元素a[i]的地址,或者说,它们指向a数组序号为i的元素。[ ]实际上是变址运算符,即
将a[i]按a+i计算地址,然后找出此地址单元中的值(先找到a地址,然后再找a+i的地址)。
————指向多维数组的指针
————指针数组
————多级指针
————指针数组做main函数的形参 ————这几个理解的不透彻,就不写了
—通过指针引用字符串
# include
int main (void)
{
char * string \ " I love China!";
printf("%s\n",string);
return 0;
}
运行:
I love China!
注意:string被定义为一个指针变量,基类型为字符型。请注意它只能指向一个字符类型数据,而不能同时指向多个字符数据,
更不是把“I love China!”这些字符存放到string中(指针变量只能存放地址),也不是把字符串赋给*string。只是把字符串
的第一个字符的地址赋给了指针变量*string。
—使用字符指针变量和字符数组的比较
1、字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址(字符串第一个字符的地址),绝不是
将字符串放到字符指针变量中。
2、赋值方式。可以对字符指针变量赋值,但不能对数组名赋值。
3、存储单元的内容,编译时为字符数组分配若干存储单元,以存放个元素的值,而对字符指针变量,只分配了一个存储单元。
4、指针变量的值是可以改变的,而数组名代表一个固定的值,不能改变。
—动态内存分配与指向它的指针变量
什么是内存的动态分配
全局变量是分配在内存中的静态存储区的,飞静态的局部变量(包括形参)是分配在内存中的动态存储区的,这个存储区是一个
称之为——栈——的区域,为变量分配存储空间。
除此之外,C语言还允许建立内存动态分配区域,以存放一些临时用的数据,这些数据不必在程序的声明部分定义,也不必等到
函数结束时才释放,而是需要时随时开辟,不需要时随时释放,这些数据是临时存放在一个特别的自由存储区称为——堆——区
域,可以根据需要,向系统申请所需大小的空间(malloc函数)。由于未在声明部分定义它们为变量或数组,因此不能通过变量
名或数组名去引用这些数据,只能通过指针来引用,malloc函数的定义:
int *p;
*p = (int*)malloc(sizeof(int));
怎样建立内存的动态分配
对内存的动态分配是通过系统提供的库函数来实现的,主要有malloc,calloc,free,realloc这四个函数。
1、使用 malloc 函数
其函数原型为:
(void *) malloc(unsigned int size)
其作用是在内存的动态存储区中分配一个长度为size的连续空间。形参size的类型为无符号整型(不允许为负数)。此函数的值
(即返回值)是所分配区域的第一个字节的地址,或者说,此函数是一个指针型函数,返回的指针只想该分配区域的开头位置
2、使用 free 函数
其函数原型为
void free(void *p);
其作用是释放指针变量p所指向的动态空间,使这部分空间能重新被其他变量使用。
第9章 用户自己建立数据类型
—定义结构体的方式
1、 定义结构体数组
struct student //struct student 是用户自定义的一种数据类型
{
int age;
float score;
}
struct student s1 = {19,92.5} ;
2、
struct student
{
int age;
float score;
} s1; //只能定义一次
—怎样使用结构体变量
1、赋值和初始化
定义的同时可以整体赋初值
如果定义完之后,只能单个的赋初值。
struct student //struct student 是用户自定义的一种数据类型
{
int age;
float score;
}
struct student s1 = {19,92.5} ; //定义的同时赋初值
struct student s2; //定义
s2.age = 10; //分开赋值
s2.score = 80.5;
printf("%d %f\n",s1.age,s1.score);
printf("%d %f\n",s2.age,s2.score);
—结构体指针
所谓结构体质针就是指向结构体变量的指针,一个结构体变量的起始地址就是这个结构体变量的指针。如果把一个结构体变量的起
始地址存放在一个指针变量中,那么这个指针变量就指向该结构体变量。
指向结构体变量的指针:
指向结构体对象的指针变量既可指向结构体变量,也可指向结构体数组中的元素。指针变量的基类型必须与结构体变量的类型
相同。例如:
struct student * p; // p可以指向struct student类型的变量或数组元素
# include
# include
struct student
{
int age;
char name[10];
};
int main (void)
{
struct student s1;
struct student *s2;
s2 = &s1; // s2 指向 s1,必须加&取地址符号
s1.age = 18;
strcpy(s1.name,"wang");
printf("%d %s\n",s1.age,s1.name);
printf("%d %s\n",(*s2).age,(*s2).name);
return 0;
}
说明:为了方便和直观,C语言允许把(*p).name用p->num来代替,“->”代表一个箭头,p->num表示p所指向的结构体变量中的num
成员。"->"称为指向运算符。
如果p指向一个结构体变量s1,以下三种方法等价:
1、s1. 成员名
2、(*p). 成员名
3、p -> 成员名
—用指针处理链表 ——见数据结构
链表有一个“头指针”变量,它存放一个地址,该地址指向一个元素。链表中每一个元素称为“结点”,每个结点都应包括两个部分:
(1)用户需要用的实际数据;(2)下一个结点的地址。可以看出,头指针指向第1个元素,第1个元素又指向第2个元素……直到
最后一个元素,该元素不再指向其他元素,它称为“表尾”,它的地址部分存放一个“NULL”(表示空地址),链表到此结束。