十五、关键字
auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while
1. typedef 关键字
typedef 关键字的作用: 可以将类型重命名。
#include <stdio.h> // 将 unsigned int 类型重命名为 u_int typedef unsigned int u_int; int main() { unsigned int a = 0; u_int b = 0; // a 和 b 的类型是等价的 return 0; }
2. register 关键字
register 关键字: register 关键字主要与寄存器有关。
下图是计算机存储数据常见的结构,其中寄存器就是直接集成在 CPU 上的,在过去的时代中,CPU 拿放数据都是与内存进行沟通;随着硬件的发展,CPU 的处理速度越来越快,而现在,由于寄存器的读写速度更快,CPU 可以直接对寄存器进行读写。如果 CPU 想要新的数据,那么内存再逐级往上替换,先替换缓存中的数据,再由缓存中的数据替换掉寄存器中的数据,这样一来,寄存器的数据可以一直保持最新的。
所以,寄存器的作用主要就是提高了 CPU 的处理速度。
鉴于此,C语言 就为我们提供了 register 关键字,可以让我们将变量直接放入寄存器中。但虽说提供了此关键字,但到最后是由编译器决定是否真正地放入寄存器中,因为寄存器的容量非常小,不可能随心所欲。
此外,寄存器变量不能使用 & 取地址,因为涉及到地址,只有内存中才会有。
register int a = 10;
3. static 关键字
(1) static 修饰局部变量
程序清单:
#include <stdio.h> void test() { int n = 1; printf("%d ", ++n); } int main() { for (int i = 0; i < 10; i++) { test(); } return 0; }
输出结果:
程序清单:
#include <stdio.h> void test() { static int n = 1; // 将局部变量设置成 static printf("%d ", ++n); } int main() { for (int i = 0; i < 10; i++) { test(); } return 0; }
输出结果:
总结:
对比上面的两个程序,得出总结:
当 static 关键字修饰一个局部变量的时候,就改变了局部变量的存储类型。原本一个局部变量是存储在栈区的,但被 static 修饰后,它就存储在了静态区。所以,被 static 修饰的局部变量,它的生命周期更长,可以被视为整个工程的生命周期。
也就是说,被 static 关键字修饰的局部变量,它出了局部作用域的范围不再被销毁,但它的作用域范围没有被改变。
分析代码: 局部变量 n 的作用域依然是 test 函数的内部,但 n 经初始化,并出了作用域后,不会被销毁。(下面的第三行初始化代码只用了一次)
void test() { static int n = 1; printf("%d ", ++n); }
(2) static 修饰全局变量
程序清单:
程序清单:
总结:
对比上面的两个程序,得出总结:
① 全局变量本身就具有外部链接属性,当一个 .c 文件用到了另一个 .c 文件的局部变量时,只需要在前者加上 extern 关键字声明即可。
② 而 static 修饰全局变量时,就将全局变量原本的外部链接属性变成了内部链接属性,所以后来被 static 修饰的全局变量只能在本 .c 文件中使用。
③ 综上所述,static 修饰全局变量的使用场景就是,在一个工程中,你防止一个 .c 文件篡改了另一个 .c 文件的全局变量数据。但实际上,全局变量用的并不多,因为使用它时,本身就会带来使用变量作用域混乱的问题。
(3) static 修饰函数
static 修饰函数和 static 修饰全局变量的思想差不多。当一个函数被 static 修饰,使得这个函数只能在本源文件内使用,不能在其他源文件内使用,即使两个源文件属于同一个工程也不行。
十六、指针
1. 指针与内存
指针就是地址,有了地址,就能帮助我们快速地找到一块内存空间。
#include <stdio.h> int main() { int a = 10; int* pa = &a; // 取出 a 的地址赋值给指针变量 pa *pa = 20; // *pa == a printf("%d\n", a); return 0; }
输出结果:
在上面的程序中,
&a 表示取出 int变量 a 的地址 (取出的是 变量a 的第一个字节地址);
*pa 表示解引用 指针变量 pa,*pa 就等价于 a.
如下图所示:(假设虚拟地址空间为 32位)
注意事项:
内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。为了能够有效访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。在 C语言中,每创建一个变量就会在底层开辟地址。
① 内存会被划分为小的内存单元,一个内存单元的大小是1个字节。
② 每个内存单元都有编号,这个编号也被称为:地址 / 指针。
③ 地址 / 指针可以存放在一个变量中, 这个变量称为指针变量,指针变量也是一个变量,它也有自己的地址。
④ 通过指针变量中存储的地址,就能找到指针指向的空间。
2. 指针变量的大小
程序清单:
#include <stdio.h> int main() { int a = 10; char ch = 'a'; double d = 3.14; int* pa = &a; char* pc = &ch; double* pd = &d; printf("%d\n", sizeof(pa)); printf("%d\n", sizeof(pc)); printf("%d\n", sizeof(pd)); return 0; }
输出结果:
结论:
指针变量是用来存放地址的。所以,地址的存放需要多大空间,指针变量的大小就应该是多大。
① 32位 机器,支持 32位 虚拟地址空间,其产生的地址就是 32位,所以此时指针变量就需要 32位 的空间存储,即 4字节。
② 64位 机器,支持 64位 虚拟地址空间,其产生的地址就是 64位,所以此时指针变量就需要 64位 的空间存储,即 8字节。
十七、结构体
结构体是 C语言 中的自定义类型,它可以用来描述一个包含多种类型的信息。比方说:一名学生的信息 (名字、年龄、学号)
C语言 的结构体和 Java 中的类差不多,我们刚开始创建的结构体的时候,它就相当于是一块空白的模板,我们可以通过这个模板再引申同种类别的东西。例如,下面的 Student 就相当于一个类型,它的地位与 int、char 差不多,只不过 Student 类型是由我们自己自定义的类型。
程序清单1
#include <stdio.h> struct Student { char name[20]; // 名字 int age; // 年龄 int studentID; // 学号 }; int main() { struct Student student1 = {"Jack", 18, 32}; struct Student student2 = {"Bruce", 20, 05}; printf("%s %d %d\n", student1.name, student1.age, student1.studentID); struct Student* ps1 = &student1; printf("%s %d %d\n", (*ps1).name, (*ps1).age, (*ps1).studentID); printf("%s %d %d\n", ps1->name, ps1->age, ps1->studentID); return 0; }
输出结果:
注意事项:
在访问结构体成员时,可以采用 . 或者 -> ,前者对应结构体变量,后者对应结构体指针变量。
程序清单2
#include <stdio.h> #include <string.h> struct Student { char name[20]; // 名字 int age; // 年龄 int studentID; // 学号 }; int main() { struct Student student1 = { "Jack", 18, 32 }; printf("%s %d %d\n", student1.name, student1.age, student1.studentID); //student1.name = "小红"; // error student1.age = 17; student1.studentID = 25; strcpy(student1.name, "小红"); printf("%s %d %d\n", student1.name, student1.age, student1.studentID); return 0; }
输出结果:
注意事项:
上面的程序中,有一处代码是错误的,因为在 C语言 中,没有字符串类型,它并不像 Java 直接就能修改。由于 name 变量本身是一个字符数组,所以下面的代码使用的其实是数组名,即数组首元素地址。所以从类型的角度来看,字符串 " 小红 " 不能直接赋值给一个指针变量,所以编译器就会直接报错。
student1.name = "小红"; // error
十八、C语言 的内存布局
在我们平时写程序时,创建的变量都会在底层开辟内存。C语言 的内存布局分为三大块:栈区、堆区、静态区。
备注:栈区是我们平时使用最多的区域。栈区的使用习惯:先使用高地址处的空间,再使用低地址处的空间。