c语言的作用域、变量与结构体
一、变量的作用域
根据变量的作用域,可以分为:
1.局部变量:
1> 定义:在函数(代码块)内部定义的变量(包括函数的形参)
2> 作用域:局部变量只有在定义它的函数内部使用,其它函数不能使用它。从定义变量的那一行开始,一直到代码块结束
3> 生命周期:从定义变量的那一行开始分配存储空间,代码块结束后,就会被回收
4> 没有固定的初始值
2.全局变量
1> 定义:在函数外面定义的变量
2> 作用域:从定义变量的那一行开始,一直到文件结尾(能被后面的所有函数共享)
3> 生命周期:程序一启动就会分配存储空间,程序退出时才会被销毁
4> 默认的初始值就是0
1 int a; 2 3 int main () 4 { 5 int b; 6 return 0; 7 }
第1行的变量a是全局变量,第7行的变量b是局部变量。
1 // 全局变量:a、b、c 2 // 局部变量:v1、v2、e、f 3 4 #include <stdio.h> 5 // 变量a的初值是10 6 int a = 10; 7 8 // 变量b的初值是0 9 // 变量c的初值是20 10 int b , c = 20; 11 12 int sum(int v1, int v2) 13 { 14 return v1 + v2; 15 } 16 17 void test() 18 { 19 b++; 20 21 int i = 0; 22 i++; 23 24 printf("b=%d, i=%d\n", b, i); 25 } 26 27 int main() 28 { 29 test(); 30 test(); 31 test(); 32 33 int e = 10; 34 35 { 36 { 37 int f = 30; 38 } 39 } 40 41 return 0; 42 }
二、变量的存储类型
* 变量的存储类型就是指变量存储在什么地方。有3个地方可以用于存储变量:普通内存、运行时堆栈、硬件寄存器。变量的存储类型决定了变量何时创建、何时销毁以及它的值能保持多久,也就是决定了变量的生命周期。
* C语言根据变量的存储类型的不同,可以把变量分为:自动变量、静态变量、寄存器变量。
1.自动变量
1> 定义:自动变量是存储在堆栈中的。
2> 哪些是自动变量:被关键字auto修饰的局部变量都是自动变量,但是极少使用这个关键字,基本上是废的,因为所有的局部变量在默认情况下都是自动变量。
2.静态变量
1> 定义:静态变量是存储在静态内存中的,也就是不属于堆栈。
2> 哪些是静态变量:
- 所有的全局变量都是静态变量
- 被关键字static修饰的局部变量也是静态变量
3> 生命周期:静态变量在程序运行之前创建,在程序的整个运行期间始终存在,直到程序结束。
3.寄存器变量
1> 定义:存储在硬件寄存器中的变量,称为寄存器变量。寄存器变量比存储在内存中的变量访问效率更高(默认情况下,自动变量和静态变量都是放在内存中的)
2> 哪些变量是寄存器变量:
- 被关键字register修饰的自动变量都是寄存器变量
- 只有自动变量才可以是寄存器变量,全局变量和静态局部变量不行
-
寄存器变量只限于int、char和指针类型变量使用
3> 生命周期:因为寄存器变量本身就是自动变量,所以函数中的寄存器变量在调用该函数时占用寄存器中存放的值,当函数结束时释放寄存器,变量消失。
1 int main() { 2 register int a; 3 return 0; 4 }
第2行的变量a是个寄存器变量。
4> 使用注意:
- 由于计算机中寄存器数目有限,不能使用太多的寄存器变量。如果寄存器使用饱和时,程序将寄存器变量自动转换为自动变量处理
- 为了提高运算速度,一般会将一些频繁使用的自动变量定义为寄存器变量,这样程序尽可能地为它分配寄存器存放,而不用内存
3> 生命周期:静态变量在程序运行之前创建,在程序的整个运行期间始终存在,直到程序结束。
1 #include <stdio.h> 2 3 int a; 4 5 void test() { 6 static int b = 0; 7 b++; 8 9 int c = 0; 10 c++; 11 12 printf("b=%d, c=%d \n", b, c); 13 } 14 15 int main() { 16 int i; 17 // 连续调用3次test函数 18 for (i = 0; i<3; i++) { 19 test(); 20 } 21 22 return 0; 23 }
* 第3行的变量a、第6行的变量b都是静态变量,第9行的变量c、第16行的变量i是自动变量。
* 因为第6行的变量b是静态变量,所以它只会被创建一次,而且生命周期会延续到程序结束。因为它只会创建一次,所以第6行代码只会执行一次,下次再调用test函数时,变量b的值不会被重新初始化为0。
* 注意:虽然第6行的变量b是静态变量,但是只改变了它的存储类型(即生命周期),并没有改变它的作用域,变量b还是只能在test函数内部使用。
* 我们在main函数中重复调用test函数3次,输出结果为:
一、什么是结构体
* 当一个整体由多个数据构成时,我们可以用数组来表示这个整体,但是数组有个特点:内部的每一个元素都必须是相同类型的数据。
* 在实际应用中,我们通常需要由不同类型的数据来构成一个整体,比如学生这个整体可以由姓名、年龄、身高等数据构成,这些数据都具有不同的类型,姓名可以是字符串类型,年龄可以是整型,身高可以是浮点型。
* 结构体允许内部的元素是不同类型的。
二、结构体的定义
1.定义形式
结构体内部的元素,也就是组成成分,我们一般称为"成员"。
结构体的一般定义形式为:
1 struct 结构体名{ 2 3 类型名1 成员名1; 4 5 类型名2 成员名2; 6 7 …… 8 9 类型名n 成员名n; 10 11 };
例如
struct Date { int year; int month; int day; };
struct是关键字,是结构体类型的标志。
2.举例
比如,我们定义一个学生
1 struct Student { 2 char *name; // 姓名 3 int age; // 年龄 4 float height; // 身高 5 };
上面定义了一个叫做Student的结构体,共有name、age、height3个成员。
三、结构体变量的定义
前面只是定义了名字为Student的结构体类型,并非定义了一个结构体变量,就像int一样,只是一种类型。
接下来定义一个结构体变量,方式有好多种。
1.先定义结构体类型,再定义变量
1 1 struct Student { 2 2 char *name; 3 3 int age; 4 4 }; 5 5 6 6 struct Student stu; 7
第6行定义了一个结构体变量,变量名为stu。struct和Student是连着使用的。
2.定义结构体类型的同时定义变量
1 struct Student { 2 3 char *name; 4 int age; 5 } stu;
结构体变量名为stu
3.直接定义结构体类型变量,省略类型名
1 struct { 2 3 char *name; 4 int age; 5 } stu;
结构体变量名为stu
1 /* 2 数组:只能由多个相同类型的数据构成 3 4 结构体:可以由多个不同类型的数据构成 5 */ 6 #include <stdio.h> 7 8 int main() 9 { 10 //int ages[3] = {[2] = 10, 11, 27}; 11 12 13 //int ages[3] = {10, 11, 29}; 14 15 // 1.定义结构体类型 16 struct Person 17 { // 里面的3个变量,可以称为是结构体的成员或者属性 18 int age; // 年龄 19 double height; // 身高 20 char *name; // 姓名 21 }; 22 23 // 2.根据结构体类型,定义结构体变量 24 struct Person p = {20, 1.55, "jack"}; 25 p.age = 30; 26 p.name = "rose"; 27 28 printf("age=%d, name=%s, height=%f\n", p.age, p.name, p.height); 29 30 /* 错误写法 31 struct Person p2; 32 p2 = {30, 1.67, "jake"}; 33 */ 34 35 struct Person p2 = {.height = 1.78, .name="jim", .age=30}; 36 //p2.age = 25; 37 38 return 0; 39 }
四、结构体的注意点
1.不允许对结构体本身递归定义
如下做法是错误的,注意第3行
1 struct Student { 2 int age; 3 struct Student stu; 4 };
2.结构体内可以包含别的结构体
1 #include <stdio.h> 2 3 int main() 4 { 5 struct Date 6 { 7 int year; 8 int month; 9 int day; 10 }; 11 12 13 // 类型 14 struct Student 15 { 16 int no; // 学号 17 18 struct Date birthday; // 生日 19 20 struct Date ruxueDate; // 入学日期 21 22 // 这种写法是错误的 23 //struct Student stu; 24 }; 25 26 27 struct Student stu = {1, {2000, 9, 10}, {2012, 9, 10}}; 28 29 printf("year=%d,month=%d,day=%d\n", stu.birthday.year, stu.birthday.month, stu.birthday.day); 30 31 printf("year=%d,month=%d,day=%d\n", stu.ruxueDate.year, stu.ruxueDate.month, stu.ruxueDate.day); 32 33 34 35 36 37 return 0; 38 }
3.定义结构体类型,只是说明了该类型的组成情况,并没有给它分配存储空间,就像系统不为int类型本身分配空间一样。只有当定义属于结构体类型的变量时,系统才会分配存储空间给该变量
1 struct Student { 2 char *name; 3 int age; 4 }; 5 6 struct Student stu;
第1~4行并没有分配存储空间,当执行到第6行时,系统才会分配存储空间给stu变量。
4.结构体变量占用的内存空间是其成员所占内存之和,而且各成员在内存中按定义的顺序依次排列
比如下面的Student结构体:
1 struct Student { 2 char *name; // 姓名 3 int age; // 年龄 4 float height; // 身高 5 };
在16位编译器环境下,一个Student变量共占用内存:2 + 2 + 4 = 8字节。
5.结构体类型不能重复定义
1 struct Student 2 { 3 int age; 4 }; 5 6 struct Student 7 { 8 double height; 9 }; 10 11 struct Student stu; 12
五、结构体的初始化
将各成员的初值,按顺序地放在一对大括号{}中,并用逗号分隔,一一对应赋值。
比如初始化Student结构体变量stu
1 struct Student { 2 char *name; 3 int age; 4 }; 5 6 struct Student stu = {"xiaomeng", 27}; 7
struct Student stu; stu = {"MJ", 27};
六、结构体的使用
1.一般对结构体变量的操作是以成员为单位进行的,引用的一般形式为:结构体变量名.成员名
1 struct Student { 2 char *name; 3 int age; 4 }; 5 struct Student stu; 6 7 // 访问stu的age成员 8 stu.age = 27; 9
第8行对结构体的age成员进行了赋值。"."称为成员运算符,它在所有运算符中优先级最高
2.如果某个成员也是结构体变量,可以连续使用成员运算符"."访问最低一级成员
1 struct Date { 2 int year; 3 int month; 4 int day; 5 }; 6 7 struct Student { 8 char *name; 9 struct Date birthday; 10 }; 11 12 struct Student stu; 13 14 stu.birthday.year = 1986; 15 stu.birthday.month = 9; 16 stu.birthday.day = 10;
注意第14行以后的代码
3.相同类型的结构体变量之间可以进行整体赋值
1 struct Student { 2 char *name; 3 int age; 4 }; 5 6 struct Student stu1 = {"MJ", 27}; 7 8 // 将stu1直接赋值给stu2 9 struct Student stu2 = stu1; 10 11 printf("age is %d", stu2.age);
注意第9行。输出结果为:
补齐算法
1 #include <stdio.h> 2 int main() 3 { 4 struct Student 5 {//补齐算法 6 int age;// 4个字节 7 8 char a; //1个字节 9 10 //char *name; // 8个字节 11 }; 12 13 struct Student stu; 14 //stu.age = 20; 15 //stu.name = "jack"; 16 // 补齐算法(对齐算法) 17 // 结构体所占用的存储空间 必须是 最大成员字节数的倍数 18 19 int s = sizeof(stu); 20 printf("%d\n", s); 21 22 return 0; 23 }
七、结构体数组
1.定义
跟结构体变量一样,结构体数组也有3种定义方式
struct Student { char *name; int age; }; struct Student stu[5]; //定义1
struct Student { char *name; int age; } stu[5]; //定义2
struct { char *name; int age; } stu[5]; //定义3
上面3种方式,都是定义了一个变量名为stu的结构体数组,数组元素个数是5
2.初始化
struct { char *name; int age; } stu[2] = { {"MJ", 27}, {"JJ", 30} };
也可以用数组下标访问每一个结构体元素,跟普通数组的用法是一样的
举例
1 #include <stdio.h> 2 int main() 3 { 4 struct RankRecord 5 { 6 int no; // 序号 4 7 char *name; // 名称 8 8 int score; // 积分 4 9 }; 10 11 //int ages[3] = {10, 19, 29}; 12 13 //int ages[3]; 14 // 对齐算法 15 // 能存放3个结构体变量,每个结构体变量占16个字节 16 // 72 17 struct RankRecord records[3] = 18 { 19 {1, "jack", 5000}, 20 21 {2, "jim", 500}, 22 23 {3, "jake",300} 24 }; 25 26 records[0].no = 4; 27 // 错误写法 28 //records[0] = {4, "rose", 9000}; 29 30 for (int i = 0; i<3; i++) 31 { 32 printf("%d\t%s\t%d\n", records[i].no, records[i].name, records[i].score); 33 } 34 35 36 //printf("%d\n", sizeof(records)); 37 38 39 return 0; 40 }
八、结构体作为函数参数
将结构体变量作为函数参数进行传递时,其实传递的是全部成员的值,也就是将实参中成员的值一一赋值给对应的形参成员。因此,形参的改变不会影响到实参。
1 #include <stdio.h> 2 3 // 定义一个结构体 4 struct Student { 5 int age; 6 }; 7 8 void test(struct Student stu) { 9 printf("修改前的形参:%d \n", stu.age); 10 // 修改实参中的age 11 stu.age = 10; 12 13 printf("修改后的形参:%d \n", stu.age); 14 } 15 16 int main(int argc, const char * argv[]) { 17 18 struct Student stu = {30}; 19 printf("修改前的实参:%d \n", stu.age); 20 21 // 调用test函数 22 test(stu); 23 24 25 printf("修改后的实参:%d \n", stu.age); 26 return 0; 27 }
* 首先在第4行定义了一个结构体类型Student
* 在第18行定义了一个结构体变量stu,并在第22行将其作为实参传入到test函数
输出结果为:,形参是改变了,但是实参一直没有变过
九、指向结构体的指针
* 每个结构体变量都有自己的存储空间和地址,因此指针也可以指向结构体变量
* 结构体指针变量的定义形式:struct 结构体名称 *指针变量名
* 有了指向结构体的指针,那么就有3种访问结构体成员的方式
- 结构体变量名.成员名
- (*指针变量名).成员名
- 指针变量名->成员名
1 #include <stdio.h> 2 int main() 3 { 4 struct Student 5 { 6 int no; 7 int age; 8 }; 9 // 结构体变量 10 struct Student stu = {1, 20}; 11 12 // 指针变量p将来指向struct Student类型的数据 13 struct Student *p; 14 15 // 指针变量p指向了stu变量 16 p = &stu; 17 18 p->age = 30; 19 20 // 第一种方式 21 printf("age=%d, no=%d\n", stu.age, stu.no); 22 23 // 第二种方式 24 printf("age=%d, no=%d\n", (*p).age, (*p).no); 25 26 // 第三种方式 27 printf("age=%d, no=%d\n", p->age, p->no); 28 29 return 0; 30 }
输出结果:age=30, no=1
age=30, no=1
age=30, no=1