3.2.1 常量和变量
常量
C语言常量分为直接常量和符号常量两种。
直接常量
直接常量又分为整型常量,实型常量,字符型常量和字符串常量。
1.整型常量
85 /* 十进制 */
0213 /* 八进制 */
0x4b /* 十六进制 */
30 /* 整数 */
30u /* 无符号整数 */
30l /* 长整数 */
30ul /* 无符号长整数 */
2.实型常量
实数 浮点数
(1)小数形式:
(2)指数形式:±尾数E指数。
3.14159 /* 合法的 */
314159E-5L /* 合法的 */
510E /* 非法的:不完整的指数 字母E或e的前后必须有数字 */
210f /* 非法的:没有小数或指数 小数点不可以省略*/
.e55 /* 非法的:缺少整数或分数 字母E或e的前后必须有数字*/
3.字符型常量
用英文单引号括起来,只保存一个字符’a’、‘b’ 、‘*’ ,还有转义字符 ‘\n’ 、‘\t’。
4.字符串常量
用英文的双引号引起来 可以保存多个字符。
"hello, dear"
"hello, \
dear"
"hello, " "d" "ear" //这三种形式所显示的字符串是相同的。
在内存中占用一段连续的存储单元,系统自动在字符串的尾部加上’\0’作为字符串的结束标志,因此,n个字符组成的字符串,在内存中占用n+1个字节空间。可以使用sizeof运算符来计算字符串占用的内存空间大小。
字符串的长度等于该字符串中所包含的有效字符个数.如" Hello World"的长度为10.转义字符可以作为一个字符,如:“Hello World\t"的长度为11。strlen()函数来计算字符串长度。
字符常量和字符串常量在内存中的存储情况是不同的,如‘6’在内存中占1个字节,存储的是ASCII码,而”6“在内存中占2个字节,一个字节存储‘6’,另一个字节存储‘\0’。可以把一个字符常量赋予一个字符变量,但不能把一个字符串常量赋予给一个字符常量,在C语言中,由于没有提供字符串类型的变量,字符串一般用字符数组来解决。
符号常量
#define 标识符 常量值
符号常量不占内存 习惯上用大写标识符
变量
常变量(c99)
const int Max=100;
符号常量不占用内存空间,在预编译时就全部由符号常量的值替换了;而常变量占用内存空间,此变量在存在期间不能重新赋值。
在对象名前使用const声明常对象,但声明时必须同时进行初始化,而且不能被更新。
标识符
参见文章:C语言编程规范 — 标识符的命名规则
3.2.2 数据类型
1 基本类型:
算术类型,包括两种类型:整数类型和浮点类型。
2 枚举类型:(enum)
它们也是算术类型,被用来定义在程序中只能赋予其一定的离散整数值的变量。
3 void 类型:
类型说明符 void 表明没有可用的值。
4 派生类型:
它们包括:指针类型、数组类型、结构类型、共用体类型和函数类型。
详细内容查看链接:C语言数据类型
第7章 函数
7.4 函数声明
参见链接:函数声明应该写在什么位置?main函数里面还是前面?、
7.7 数组作函数参数
数组元素做函数实参,不能做形参。
传递的数组元素的值。
一维数组作函数参数,数组名做函数实参和形参。
传递的是数组首元素的地址。所以数组名做函数实参,形参必须是对应的数组名或是指针变量。
void fun1(char *a,int length); //数组名 形参一维数组的声明中可以写元素个数length,也可以不写。
void fun2(char a[],int length); //数组名 a[]中方括号内的数值并无实际作用,编译系统对一维数组方括号内的内容不予处理
void fun3(char a[10],int length); //数组名
//调用时
fun1(array,n);
数组首地址传给被调用的函数后,由于是地址传递,相当于形参实参的两个数组共用同一段内存单元,所以改变形参的值就会把实参的值改变掉。值传递是不会改变形参的值的,因为形参不会占用内存,只是临时的存储单元。
二维数组作函数参数,数组名作为实参和形参。
在对形参数组声明时,必须指定列的大小,且应与实参的列大小相同。
第一维的大小可以忽略,形参实参的一维大小可以不同。
int array[3][10]; //形参数组的两个维都指定
int array[][10]; //第一维大小省略 二者都合法而且等价,但是不能把第二维的大小省略。
/* 下面写法不合法 */
int array[][]; //不能确定数组的每一行有多少列元素
int array[3][]; //不指定列数就无法确定数组的结构
7.8 局部变量和全局变量
局部变量优先原则:当局部变量与全局变量同名时,在该函数或者复合语句内,局部变量优先于全局变量。
建议多使用局部变量,因为局部与全局的内存存储方式不同,全局变量存储在全局数据区中,局部变量存储在栈区。局部变量仅仅在使用时才会开辟存储单元,随函数的退出或循环退出就不存在了,而全局变量的生命期和主程序一样,随程序的销毁而销毁。
其次,如果使用了全局变量,会降低程序的可靠性和移植性。违背了程序“高内聚,低耦合”的模块要求。一般要求把C语言的函数要做成一个封闭体,除了“实参—形参”的渠道与外界产生联系以外,没有其他渠道。
最后全局变量的使用会降低程序的清晰性和可阅读性,容易出错。
7.9 变量的存储方式和周期
1、auto 自动变量
int f(int a)
{
auto int b,c=3; /*定义b,c自动变量*/
}
执行完f函数后,自动释放a,b,c所占的存储单元。关键字auto可以省略,auto不写则隐含定为“自动存储类别”,属于动态存储方式。
作用域:函数内
2、register 寄存器变量
将变量存在寄存器中,减少访问内存的次数,提高效率。但现在的编译器优化到自动将变量存放在寄存器中了,不需要程序员来指定,所以平时使用性不大。
作用域:函数内,但是会保留变量值。
3、extern 外部变量 [声明]
1)在一个文件中扩展外部变量的作用域;
#include <stdio.h>
int main(){
extern int a; //不加extern关键字时a=0 是声明,不是定义
printf("a = %d",a);
}
int a = 520; //定义
2)扩展外部变量的作用域到其他文件;
file2.c中定义了变量aint a = 520;
,在file1.c中使用变量a:
#include <stdio.h>
extern a;
int main(){
printf("a = %d\n",a);
return 0;
}
作用域:函数内+函数外,变量值保留,随程序结束再释放。
4、static 静态变量
1) 对局部变量使用static关键字,会保留住变量的值,将其存储在静态存储区,不会随当前函数结束而释放内存单元。
2) 对全局变量使用static关键字,该变量的作用域只存在于当前文件模块。
7.10 内部函数和外部函数
内部函数:只能被本文件中其他函数调用,在函数名和函数类型的前面加上 static
//第一个文件
#include<stdio.h>
void show()
{
printf("%s \n","first.c");
}
//第二个文件
#include<stdio.h>
static void show()
{
printf("%s \n","second.c");
}
void main()
{
show();
}
运行结果:second.c
外部函数:需要调用其他源文件中的函数,调用外部函数之前,需要在当前源文件中定义外部函数,添加extern关键字。
//第一个源文件
int add(int x,int y)
{
return x+y;
}
//第二个源文件
#include <stdio.h>
extern int add(int x,int y);
void main()
{
printf("sum=%d\n",add(1,2));
}
第8章 指针
8.2 指针变量
指针:内存地址信息和指向数据的变量类型—带类型的地址
指针变量:地址变量,用来存放地址(指针)。
指针变量的引用
void Pointer(){//使用指针变量
int a=100,b=30;
int *p1,*p2; //定义指针变量:类型名 * 变量名;
p1=&a; p2=&b; //引用指针变量①:给指针变量赋值
printf("a=%d,b=%d\n",a,b);
printf("*p1=%d,*p2=%d\n",*p1,*p2); //引用指针变量②:引用指针变量指向的变量
printf("a的地址%d,b的地址%d\n",p1,p2); //引用指针变量③:引用指针变量的值
}
//运行结果
a=100,b=30
*p1=100,*p2=30
a的地址6421980,b的地址6421976
指针变量作为函数参数
作用:将一个变量的地址传送到另一个函数中。
在被调函数的执行过程中,应使指针变量所指向的参数值发生变化,这样,函数在调用结束后,其变化值才能保留回主调函数。
函数调用不能改变实参指针变量的值,但可以改变实参指针变量所指向变量的值。
void swap1(int *pa,int *pb){
int temp;
temp = *pa; //将a的值先保存起来
*pa = *pb; //将b的值交给a
*pb = temp; //再将保存起来的a的值交给b
}
void changePionter(void){//引用指针变量交换ab的值
int a = 5, b = 9;
int *pa = &a, *pb = &b; //定义变量同时初始化
printf("a=%d, b=%d\n", a, b);
swap1(pa,pb);
printf("a=%d, b=%d\n", a, b);
}
void main()
{
changePionter();
}
//运行结果
a=5, b=9
a=9, b=5
将上述代码的调用换成swap2()函数则不会改变ab的值。
下面这段代码交换了两个指针变量的值,但是由于调用函数是值传递方式,所以main()函数中指针变量并不会改变值。
void swap2(int *pa,int *pb){
int *temp;
temp = pa; //将a的地址先保存起来
pa = pb; //将b的d交给a
pb = temp; //再将保存起来的a的值交给b
}
//运行结果
a=5, b=9
a=5, b=9
8.3 通过指针引用数组
数组元素的指针:数组元素的地址
void PionterArray(){
int a[10]={14,3,4,5,6,8,9,9,12,13};
int b[10]={21,3,4,5,6,8,9,9,12,13};
int *p;p=&a[0]; //a[0]的地址<=>int *p=&a[0];
int *q=b; //程序中的数组名代表首元素的地址 代表把数组b的首元素(下标0)地址赋给指针变量q
printf("a[0]=%d,b[0]=%d\n", *p,*q);
}
引用数组元素时的运算
当指针指向数组元素时,可以对指针进行加减运算。
- 指针的加减
void PionterOperation(){
/* 指针自加自减运算 */
int i, *iPointer = &i;
short j, *sPointer = &j;
double k, *dPointer = &k;
scanf("%d%d%d", iPointer, sPointer, dPointer);
printf("\nint:\n");
printf("p = %d\n", iPointer);
iPointer ++;
printf("p++ = %d\n", iPointer);
printf("\nshort:\n");
printf("p = %d\n", sPointer);
sPointer ++;
printf("p++ = %d\n", sPointer);
printf("\ndouble:\n");
printf("p = %d\n", dPointer);
dPointer ++;
printf("p++ = %d\n", dPointer);
}
//输入:
100 300 500
//运行结果
int:
p = 6421972
p++ = 6421976 //基本整形变量i在内存中占4个字节
short:
p = 6421970
p++ = 6421972 //短整型变量j在内存中占2个字节
double:
p = 6421960
p++ = 6421968 //双精度变量k在内存中占8个字节
- 数组指针的加减运算
如果指针变量p已经指向数组中的第一个元素,则p+1指向同一数组中的下一个元素,p-1指向同一数组中的上一个元素。
p+1实际上是p+1*d,d表示该数组元素所占的字节数。
void PtArrayOperation(){
int urn[5] = {100,200,300,400,500};
int *ptr1,*ptr2,*ptr3,*ptr4,*ptr5,*ptr6;
printf("urn[5] = { ");
for(int j=0;j<5;j++){
printf("%d ",urn[j]);
}
printf("};\n");
ptr1 = urn; //将数组首地址赋给指针
ptr2 = &urn[2]; //将数组中第三个元素的地址赋给指针
ptr3 = ptr1 + 4; //指针加法 数组元素urn[4]的地址
ptr4 = ptr2 - 1; //指针减法
ptr5 = ptr2 - 3; //指针指向数组第一个元素之前,那么它是非法的。
ptr6 = ptr2 + 3; //指针指向数组最后一个元素之后,仍是合法的,但不允许对其执行间接访问操作。
printf("ptr1 = %d\n", *ptr1);
printf("&urn[2]= %d\n", *ptr2);
printf("ptr1+4 = %d\n", *ptr3);
printf("ptr2 = %d\n", *ptr4);
printf("ptr2-3 = %d\n", *ptr5);
printf("ptr2+3 = %d\n", *ptr6);
}
//运行结果
urn[5] = { 100 200 300 400 500 };
ptr1 = 100
&urn[2]= 300
ptr1+4 = 500
ptr2 = 200
ptr2-3 = 0
ptr2+3 = 0
- 数组指针的关系运算
前提:两个指针都指向同一数组的元素。如果指向两个不同数组的指针进行求差运算可能会得出一个值,或者导致运行时错误。
p2-p1的结果是两个地址之差除以数组元素的长度。通常用来表示两个指针所指的元素之间差几个元素。
void main()
{
int urn[5] = {100,200,300,400,500};
int *ptr1,*ptr2;
ptr1 = &urn[0]; //将数组首地址赋给指针
ptr2 = &urn[2]; //将数组中第三个元素的地址赋给指针
printf("ptr2-ptr1 = %d\n", ptr2-ptr1);
}
//运行结果
ptr2-ptr1 = 2
注意:两个地址不能相加,比如ptr2+ptr1是无实际意义的。
通过指针引用数组元素
(1)指针法 * (a+i) 或 * (p+i)或 p[i]
(2)下标法 a[i]
数组名代表的是数组中首元素的地址。在程序编译时,a[i]是按*(a+i)处理的,即按数组元素的首地址加上相应位移量i找到新元素的地址。而p=a,即p是指向数组a的首元素的地址,因此是等价的。从这里可以看出,[ ]实际上是变地址运算符,即将a[i]按a+i计算地址,然后找此地址单元中的值。
void main()
{
int urn[5] = {100,200,300,400,500};
int *ptr1;
ptr1 = urn;
printf("指针法:*(ptr1+1) = %d\n", *(ptr1+1));
printf("指针法:*(urn+1) = %d\n", *(urn+1));
printf("指针法:ptr1[1] = %d\n", ptr1[1]); //当指针变量指向数字元素时,指针变量可以带下标。
printf("下标法:urn[1] = %d\n", urn[1]);
}
//运行结果
指针法:*(ptr1+1) = 200
指针法:*(urn+1) = 200
指针法:ptr1[1] = 200
下标法:urn[1] = 200
注意:
1.指针指向数组元素时要保证指向数组中有效的元素。
指针指向数组第一个元素之前,那么它是非法的。
指针指向数组最后一个元素之后,仍是合法的,但不允许对其执行间接访问操作。
2.当指针变量指向数字元素时,指针变量可以带下标。对p[i]处理成*(p+i)。尽量避免这种写法。
*p++的运算,优先级相等,结合方向是自右向左,要注意运算结果。
void main()
{
int urn[5] = {100,200,300,400,500};
int *ptr1 = &urn[0];
int *ptr2 = &urn[0];
int *ptr3 = &urn[0];
int *ptr4 = &urn[2];
printf("ptr1 = %d\n", *(++ptr1)); //ptr1 = 200 p的指向先++,再引用
printf("ptr2 = %d ", *(ptr2++)); //ptr2 = 100 p的指向先引用,再++ ptr2 = 200 *(ptr2++) <=> *ptr2++
printf("ptr2 = %d\n", *ptr2); //ptr2 = 200
printf("ptr3 = %d\n", ++*(ptr3)); //ptr3 = 101 p所指向的数组元素urn[0]加1
printf("ptr4 = %d ", *(ptr4--)); //ptr4 = 300 p的指向先引用,再-- ptr4 = 200
printf("ptr4 = %d\n", *ptr4); //ptr4 = 200
}
8.4 数组名做函数参数
前面7.7 章节已经讲过这部分内容,现在主要加以理解。
程序编译时是将形参变量a按指针变量处理的,所以以下写法等价。
void fun1(char *a,int length); //数组名 形参一维数组的声明中可以写元素个数length,也可以不写。
void fun2(char a[],int length); //数组名 a[]中方括号内的数值并无实际作用,编译系统对一维数组方括号内的内容不予处理
变量名的传递:值传递,形参类型变量名,传递的变量的值 ,不能改变实参的值
数组名的传递:址传递,形参类型数组名或指针变量,传递的数组首元素的地址,可以改变实参的值
实参的数组名是一个固定的地址,或者说是指针常量,形参数组名不是固定的地址,而是按照指针变量处理。
实例:
将数组a中n个整数按相反顺序存放。array[10] = { 3 7 9 11 0 6 7 5 4 2 }; => array[10] = { 2 4 5 7 6 0 11 9 7 3 };
实参是数组名 数组名做形参
//数组名做形参 实参是数组名
void inv1(int a[],int n){
//i:左边的下标 j:右边的下标 m:中间下标值 temp:交换的中间值
int temp,i,j,m;
m = (n-1)/2;
for(i=0,j=n-1;i<=m;i++,j--){
//i是第一个元素,j是最后一个元素,两两交换。每次交换以后i往后一个元素,j往前一个元素。直到中间m下标位置。
temp=a[i];
a[i]=a[j];
a[j]=temp;
}
}
void main(){
int i,array[10]={3,7,9,11,0,6,7,5,4,2};
printf("array[10] = { ");
for(int i=0;i<10;i++){
printf("%d ",array[i]);
}
printf("};\n");
inv1(array,10);
printf("array[10] = { ");
for(int i=0;i<10;i++){
printf("%d ",array[i]);
}
printf("};\n");
}
实参是数组名 指针变量做形参
//指针变量做形参 实参还是数组名
void inv2(int *a,int n){
int temp,*p,*i,*j,m = (n-1)/2;
i=a;j=a+n-1; //i:指向左侧数组元素 j:指向右侧数组元素
p=a+m; //p:中间的元素a[m]
for(;i<=p;i++,j--){
//i是第一个元素,j是最后一个元素,两两交换。每次交换以后i往后一个元素,j往前一个元素。直到中间m下标位置。
temp=*i;
*i=*j;
*j=temp;
}
}
指针变量做实参 形参使用数组名或指针变量
指针变量做实参,必须先使指针变量有确定值,指向已经定义的对象。
不管形参是数组名还是指针变量的写法,实际上都是指针变量。
void main(){
int i,array[10]={3,7,9,11,0,6,7,5,4,2};
int *p = array;
printf("array[10] = { ");
for(int i=0;i<10;i++){
printf("%d ",array[i]);
}
printf("};\n");
inv1(p,10); //指针变量做实参,必须先使指针变量有确定值,指向已经定义的对象
//inv2(p,10); //指针变量做实参,必须先使指针变量有确定值,指向已经定义的对象
printf("array[10] = { ");
for(int i=0;i<10;i++){
printf("%d ",array[i]);
}
printf("};\n");
}