目录
一、static 关键字
C语言关键字补充:
- 关键字不能自己创建
- 关键字不能做变量名
static --静态的
C语言中static可以修饰:
- 局部变量
- 全局变量
- 函数
1、static修饰局部变量
- static是C语言中项目维护、提供安全保证的一个关键字;
- static修饰的变量初始化动作永远只会初始化一次(第一次初始化)。
(1)这里我们先看一下没有使用static关键字修饰局部变量代码的运行结果:
#include<stdio.h> //void 在这里表示不需要函数返回值 void test() { int a = 1; //a是局部变量 a++; printf("%d ", a); } int main() { int i = 0; while (i < 10) { test(); i++; } return 0; }
程序运行分析:
- 程序从main函数开始执行(main函数是程序入口,无论main函数前面有没有自定义函数,程序都是从main开始执行的),当走到 while 循环中的 test 函数时调用 test 函数进入 test 函数。
- 进入到 test 函数后:创建局部变量a,将 1 赋给变量a(此时a的值为1),再往下执行a++;(a=a+1;) 此时a的值变为2,再往下执行打印a的值(输出2)。
- 程序返回main函数(将函数调用完以后程序都要返回到main函数中)。在 test 函数中a是一个局部变量,当程序离开 test 函数时变量a的生命周期就结束了,变量a的空间就会自动销毁。
- 程序返回到main函数后进行 while 循环中执行函数后面的语句 i++; 在 while 循环中 i自增1以后,进入到while循环的条件判断-->满足循环条件(i<10),满足条件再次进入循环中,走到while循环中的 test 函数,再次调用 test 函数,第二次执行 test 函数。
- 当程序再进入到 test 函数时,重新创建局部变量a,再次将 1 赋给变量a(此时a的值为1),再往下执行a++;(a=a+1;) 此时a的值变为2,再往下执行打印输出a的值(输出2),打印完a以后,a再次出 test 函数这个作用域,程序离开 test 函数,变量a的空间再次自动销毁。
- 销毁之后函数再次回到main函数,再次执行 while 循环中的函数后面的语句i++;,如果i的值一直满足循环条件(i<10)程序就会一直执行步骤1~3的操作,直到 i 的值不满足循环条件,程序退出循环。
- 因为i的值是0~9,所以 test 函数被调用了10次,所以程序最终会打印出 10 个 2。
文字叙述可能太抽象了,这里放一张图帮助大家进一步理解:
运行结果:
(2)接下来我们看一下使用static关键字修饰局部变量代码的运行结果:
#include<stdio.h> //void 在这里表示不需要函数返回值 void test() { //使用static修饰局部变量 static int a = 1; a++; printf("%d ", a); } int main() { int i = 0; while (i < 10) { test(); i++; } return 0; }
程序运行分析:
- 程序从main函数开始执行(main函数是程序入口,无论main函数前面有没有自定义函数,程序都是从main开始执行的),当走到 while 循环中的 test 函数时调用 test 函数进入 test 函数。
- 进入到 test 函数后:创建局部变量a,将 1 赋给变量a(此时a的值为1),再往下执行a++;(a=a+1;) 此时a的值变为2,再往下执行打印输出a的值(输出2)。
- 程序返回main函数(将函数调用完以后程序都要返回到main函数中)。在main函数中程序进入到while 循环中执行函数后面的语句 i++; 在 while 循环中 i自增1以后,进行while循环的条件判断-->满足循环条件(i<10),满足条件再次进入循环中,走到while循环中的 test 函数,再次调用 test 函数,第二次执行 test 函数
- 第二次进入到 test 函数时,因为变量 a 被static修饰了,所以在第一次离开 test 函数时变量a并没有被销毁,所以变量a也没有重新创建,还是保留了上一次a的值(值为2)。程序往下执行a++,此时a的值变为3。再往下执行打印输出a的值(输出3)。
- 根据结果:得出,每一次调用 test 函数,使用的变量 a 都是上一次函数调用后留下来的 a。
- main函数中while 循环了10次, test 函数被调用了10次,说以程序输出的结果是:从2开始依次递增1,输出10个数。
这里的循环与不用static修饰的循环一样。只是使用static修饰后,变量a出了 test 函数不会被销毁,而是保留了上一次函数调用后留下的值。
运行结果:
(3)static这个关键字理解起来并不容易,这里简单的说一下内存中的一些知识,进一步的理解static关键字。
内存是一块比较大的存储空间,在使用内存时会划分不同的功能区域。
在学习编程语言的时候我们只要关注一下栈区、堆区、静态区就可以了。
(4)一个变量被static修饰与没有被static修饰,二者的差异是很大的。
- 没有被static修饰的局部变量是在栈区创建的
- 被static修饰后的局部变量就变成了静态的局部变量(属于静态变量),这个时候它就会被保存在静态区(静态区存储的变量的生命周期是整个程序的生命周期,程序结束它的生命周期才会结束)。
- static修饰局部变量的时候,其实改变了变量的存储类型(将变量从栈区放到了静态区)。使得静态的局部变量出了自己的作用域也不会销毁,其实相当于改变了这个局部变量的生命周期,使变量的声明周期变长了。
上面说的这些只是阐述了:使用static修饰的局部变量,改变了变量的生命周期的本质。
- static修饰局部变量改变了变量的生命周期,让静态局部变量出了作用域依然存在,到程序结束,生命周期才结束。
看一下使用static修饰的局部变量程序的反汇编代码(只要语句执行了就会产生反汇编代码,至于反汇编代码是什么我们不需要了解):
由反汇编代码可以得出,程序在进入test函数中并没有执行 static int a = 1; 这条语句只是告诉了我们创建了一个变量a,这个变量在程序编译期间就已经开辟空间了,在调用test函数的时候就不会再重新创建变量a。
总结一下:
使用static修饰的局部变量只改变了局部变量的生命周期,不改变局部变量的作用域。
2、static修饰全局变量
- 全局变量没有声明不可以跨文件访问
- 使用extern关键字对全局变量进行声明以后才可以跨文件访问
这里补充一下变量的声明:
变量的声明(可以声明多次):
- 声明变量就是告知某个变量已经定义好了,不用再重新分配空间;
变量声明的举例说明:
- 变量的声明相当于告诉自己的朋友自己有女朋友或男朋友了,可以告诉多个人。
- static修饰全局变量,该变量只能在本文件内被访问,不能被外部其他文件直接访问(可以通过调用函数间接访问);
- 全局变量在整个工程的其他文件内部能被使用,是因为全局变量具有外部链接属性
- 当一个全局变量被static修饰的时候,这个全局变量的外部链接属性就变成内部链接属性使得这个全局变量只能在自己所在的源文件内部使用,其他文件中不可以使用。
- 给我们的感觉是使用static修饰的全局变量作用域变小了。
总结:
- static修饰全局变量,让全局变量的外部链接属性变成了内部链接属性,从而使得它的作用域变小了。(生命周期不变)
3、static修饰函数
- 函数没有声明也可以跨文件访问,但是会报警告!所以在跨文件访问函数时还是声明一下比较好!
- 如果要消除编译器的警告,对函数进行声明即可( extern void show();extern可以省略不写 )。
没有声明外部函数:
声明外部函数:
函数也是具有外部链接属性的。
补充:
(1)为什么函数没有声明也可以跨文件访问?
- 一般自定义函数在调用的地方编译时都是以某种符号的形式存在的,当程序在进行最后一步链接时如果找不到这个函数,最终编译器才会报错。换言之在编译时这个函数不存在,那么大部分编译器通常会报警告,当实际链接时编译器会帮你在其他源文件找该函数的定义,如果找到了这个函数则编译器会自动消除警告或不影响正常使用。
- 第一张图中虽然没有在main.c中声明 Add() 函数,但该函数在test.c中已定义,所以当编译test.c和main.c时,两个文件是看不到对方文件中的函数的,这时编译器就会报警告。
- 当两个源文件连接成一个文件时,main.c文件会形成.obj文件,main.c文件在找 Add() 函数时会在test.c文件对应形成的.obj文件中找到 Add() 函数,所以最终编译器不会报错,程序可以运行。
(2)为什么全局变量的声明必须加上extern关键字,而函数的声明可以不用加extern?
- 变量声明如果不加extern,在语法上也可以是变量的定义。
- 函数的定义与声明取决于有没有函数体,若有函数体则是函数的定义,若没有函数体则是函数的声明。所有函数声明写不写extern都可以,不过在函数声明时还是建议带上。
static修饰函数,该函数只能在本文将内被访问,不能被外部其他文件直接访问。
- static修饰函数的时候,函数本来也是具有外部链接属性的,被static修饰后就变成了内部链接属性,这个函数只能在自己所在的源文件内部使用,其他文件中不可以使用。给我们的感觉是改变了作用域。
二、#define 定义常量和宏
1、#define 定义标识符常量
#include<stdio.h> #define M 100 #define STR "hehe" int main() { printf("%d\n", M); printf("%s\n", STR); return 0; }
运行结果:
2、#define 定义宏
#include<stdio.h> #define ADD(x,y) ((x)+(y)) int main() { int a = 10; int b = 20; int ret = ADD(a, b); printf("%d\n", ret); return 0; }
运行结果:
宏和函数有点像,但二者是有区别的。后期会详细叙述宏和函数的区别。
三、指针
说到指针那一定是离不开内存的!
内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。 所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。 为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。
1、变量在内存中的存放:
这里以整型为例:(整型(int)定义的变量占4个字节)
2、打印一个变量的地址
#include<stdio.h> int main() { //a的地址是4个字节中第一个字节的地址 //只要知道了第一字节的地址就可以知道整个4个字节的地址 int a = 10;//a要在内存中开辟空间 //&--取地址操作符 //%p--是以地址的形式打印 printf("%p\n", &a); return 0; }
变量a在内存中的地址:
运行结果:
计算机为变量开辟内存空间是随机开辟的,说每次打印出来的地址都是不一样的。
3、使用一个变量存放地址(这个变量就是指针变量)。
#include<stdio.h> int main() { int a = 10; int* p = &a; //p就是一个指针变量 return 0; }
这里的p存放的是a的地址,所以打印p,与打印&a的结果是相同的:
放一个图帮助大家理解
通上这个图我们可以了解到指针的定义方式:
例:int * p = &a;
* 靠不靠近类型或变量都可以,这只是一个书写习惯(每个人的书写习惯都不一样)
int * p ---->变量类型为 int * , 变量名为 p (变量名可以自已定义,不一定非要定义成p)
由此也可以写出其他类型的指针,例如字符型指针:
char ch = 'a';
char* pch = &ch;
将一个变量存放到指针变量中不是为了将变量存起来,而是为了访问这个变量,或者改变这个变量。
4、指针变量的使用:
#include<stdio.h> int main() { int a = 10; int* p = &a; //p就是一个指针变量 //改变 变量a的值 *p = 20;// * --解引用操作符 printf("%d\n", a); return 0; }
运行结果:
5、指针变量总结
- 指针变量前面的 * 叫做解引用操作符 / 间接访问操作符,它的作用是通过指针变量p里面存放的地址找到它所指向的对象,所以*p就是变量a。
- 地址是放在指针变量里面的。
- 我们把地址也称为指针,指针变量是存放地址的也是存放指针的,所以它叫做指针变量。
- 当你在使用指针变量的时候其实是在使用它指向的地址。
举个例子:
- 存放变量的内存空间就像是一个房间,每个房间都是有门牌号的。门牌号就是这个变量内存空间的地址。指针变量就一张房号登记表,表中存放了每个房间的门牌号和那个房间所在的楼层(具体位置)。
- 知道了房间的门牌号,可以一个房间一个房间的找到它,也可以通过房号登记表快速的找到它。
- 变量就像是存放在房间里的,指针变量前面加一颗 * 就表示你有了这个房间的钥匙,可以通过钥匙进入到房间里访问变量或修改变量。
如果大家觉的我说的这个例子不太准确,请帮忙指正!
指针的内容有点抽象,后期还会详细的说。现在只需要理解:
- 地址又被称为指针,存放地址的变量叫做指针变量。
- 指针变量只有一个用途,就是用来存放其他变量的地址。
- 在指针变量的眼里什么都是地址。
6、指针变量的大小
前面已经了解了整型变量的大小,那么存放变量地址的指针变量大小是多少呢?
指针变量的大小取决于地址的大小
32位平台下地址是32个bit位(即4个字节)
64位平台下地址是64个bit位(即8个字节)
打印出来看一下:
32位平台下打印出来的结果:
64位平台下打印出来的结果:
四、结构体
结构体就是将不同类型的变量包含在一起。
结构体的作用:描述生活中一些复杂的对象。
例如描述一个学生:学生的姓名、学生的年龄、学生的性别 ……
1、创建结构体
//描述学生
struct Stu
{
char name[20]; //姓名
int age; //年龄
char sex[10]; //性别
};struct --->结构体关键字,Stu --->结构体类型名
2、结构体变量的使用
#include<stdio.h> //结构体 //描述学生 struct Stu { //结构体成员 char name[20]; int age; char sex[10]; }; int main() { //创建结构体变量,并初始化 struct Stu zhangsan = { "张三",20,"男" }; struct Stu lisi = { "李四",25,"保密" }; //打印结构体的数据 printf("%s %d %s\n", zhangsan.name, zhangsan.age, zhangsan.sex); printf("%s %d %s\n", lisi.name, lisi.age, lisi.sex); return 0; }
运行结果:
. 是通过结构体变量访问结构体成员
. —— 结构体成员访问操作符
使用格式:结构体变量 . 结构体成员
补充:一个汉字占两个字节
strlen 是用来求字符串的长度,使用时要引用头文件 #include<string.h>
#include<stdio.h> #include<string.h> //结构体 //描述学生 struct Stu { //结构体成员 char name[20]; int age; char sex[10]; }; int main() { //创建结构体变量,并初始化 struct Stu zhangsan = { "张三",20,"男" }; struct Stu lisi = { "李四",25,"保密" }; struct Stu * p = &lisi; //打印结构体的数据 printf("%s %d %s\n", (*p).name, (*p).age, (*p).sex); printf("%s %d %s\n", p->name, p->age, p->sex); return 0; }
运行结果:
-> 是通过结构体指针访问结构体成员
-> —— 结构体成员访问操作符
使用格式:结构体指针 -> 结构体成员
五、总结
初识C语言系列到这就已经结束,不过对于C语言的学习才刚刚开始。
初识C语言这个系列只是将C语言的内容大致了解一下,认识一下C语言中都有哪些内容。
有些内容如果看完以后还是不明白也没关系,后期都会详细的说!
最好的学习方法就是迭代!编程语言的学习就是要多写代码。