一、修饰变量的关键字
1.auto自动变量
总结:auto可以看成是局部变量。auto是旧版C语言用来表示局部变量的。如果那些局部变量[没有显式地使用存储类别说明符(也就是数据类型)来修饰],那么将默认使用
auto
存储类别说明符。在现代的 C 语言编译器中,不必显式使用auto
关键字,因为局部变量的存储类别默认就是auto
。下面有一些详解:
只使用auto修饰变量,变量的类型默认为整型
自动变量也称局部变量。
将不在任何类、结构、枚举、联合和函数中定义的变量视为全局变量,而在函数中定义的变量视为局部变量。所有局部变量默认都是auto,一般省略不写。
auto声明全局变量时,编译会出错声明局部变量时,正常运行
#include <stdio.h>
int main() {
//num1为整型
auto int num1;
//num2为整型(当不写数据类型,直接使用auto修饰变量的时候,变量为整型)
auto num2;
return 0;
}
2.static 静态变量
修饰的对象有:局部变量、全局变量、函数
修饰局部变量,改变局部变量的存储位置
如果未初始化存储到bss段,并且值为0
如果已初始化了在data段
生存周期:局部静态变量的生存周期到整个进程结束。
作用域:在本函数内
ps:可能有人疑问,生存周期和作用域的区别
以下面代码为例,
当第一次调用test()函数,静态局部变量num1被赋初始值,局部变量num2也被赋初始值,num1、num2自增,输出的结果为:num1 = 11, num2 = 11
当第二次调用test()函数,静态局部变量num1不再赋初始值,局部变量num2被赋初始值,num1、num2自增,输出的结果为:num1 = 12, num2 = 11
当第三次调用test()函数,静态局部变量num1不再赋初始值,局部变量num2被赋初始值,num1、num2自增,输出的结果为:num1 = 13, num2 = 11
void test() { static int num1 = 10; int num2 = 10; num1++; num2++; printf("num1 = %d, num2 = %d", num1, num2); }
也就是说,局部静态变量只在函数里初始化一次,然后值将被保存,函数调用结束,局部静态变量的值将被保存,不会被销毁。之后调用该函数,将不再对局部静态变量初始化。
如果修饰全局变量,那么改变全局变量的作用域到本文件
使用static修饰全局变量,改变的只有作用域,也就是只能在本文件中使用
如果修饰函数,那么也会改变这个函数的调用权限,只能在本文件中调用了
使用static修饰函数,改变的只有作用域,也就是只能在本文件中使用
3.const 只读变量
限制其变量的值不能再通过变量名来修改
4.volatile易失变量
防止编译器过度优化
volatile
关键字修饰变量的时候,告诉编译器该变量的值可能会被意外地修改,因此需要在每次使用该变量时都重新读取它的值,而不是使用缓存中的旧值。这种被volatile关键字修饰的变量称为易失变量。
如果不使用
volatile
关键字修饰变量,在多线程或多进程的情况下,变量的值可能被其他线程或进程修改,如果此时你读取被修改的变量,读取的可能是没有改变时候的量。
5.register 寄存器变量
普通变量都存储在内存,寄存器变量存储在寄存器中,寄存器相对于内存效率要更高,但是寄存 器有限的。
register
关键字用来提高提高变量的访问速度。使用
register
关键字声明的变量具有以下特性:
register
关键字只是一个建议:register
关键字只是向编译器发出的一个建议,告诉它将该变量存储在寄存器中。编译器是否采纳这个建议取决于编译器本身以及当前的编译环境。无法获取寄存器变量的地址:由于寄存器是位于 CPU 内部的一种特殊存储区域,因此无法获取寄存器变量的地址。因此,对
register
变量使用&
运算符取地址时会导致错误。可能在栈上分配内存:尽管使用
register
关键字可能会将变量存储在寄存器中,但编译器不一定遵循这个建议。如果编译器认为将变量存储在寄存器中没有明显的性能优势,或者寄存器数量不足,它可能会选择在栈上分配内存。并非所有类型的变量都适合作为寄存器变量:通常,较小的基本类型(如
int
、char
、short
)或指针类型的变量适合作为寄存器变量,而较大的结构体或数组则不太适合。因此,对于较大的复杂类型,编译器可能会忽略register
关键字。由于现代编译器在优化代码方面已经非常强大,能够自动进行寄存器分配和优化,在大多数情况下,手动使用
register
关键字并不会带来明显的性能改进。因此,现代编程中很少使用register
关键字,而是依赖编译器的自动优化。
6.extern外部变量
主要用于函数或者是全局变量的外部(其他文件中)声明
extern
只是一个声明,而非定义:extern
关键字只是向编译器声明变量的存在和类型,而不是对变量进行实际的分配空间。因此,在使用extern
关键字声明外部变量时,不会为该变量分配内存。(另一个源文件调用extern修饰的变量时,不会为变量再次分配内存)外部变量的定义:要使用
extern
关键字声明的变量,必须在另一个源文件中进行定义。在另一个源文件中,应该使用正常的方式来定义该全局变量。外部变量的链接性:通过
extern
声明的外部变量具有外部链接性,这意味着它可以被其他源文件访问。如果希望限制外部变量的可见性,可以使用static
关键字将其声明为静态变量,使其具有内部链接性
二、#define与typedef的异同
相同点:
- 都可以在代码中自定义名称。
- 都是在编译前的预处理阶段,进行的代码替换。
不同点:
替换的程度不一样,#define只是进行简单的代码替换,typedef是创建的类型的别名
//例如
#define INT_1 int *
typedef INT_2 int *
int main()
{
INT_1 num1, num2; //#define的定义:int * num1; int num2;
INT_2 num3, num4; //typedef的定义:int * num3; int *num4;
return 0;
}
三、C的存储空间布局
- 内核段
- 栈
- 局部变量,函数的形参
- 未初始化值是随机值
- 生存周期随着函数调用结束而终止
- 堆
- 数据段(生存周期整个进程)
- bss
- 未初始化的全局变量和局部静态变量
- 初始值为0
- data
- 已初始化的全局变量和局部静态变量
- 文本段
四、指针
1.指针就是存储地址的变量,实际上指针就是地址,地址就是编号。
2.指针的类型取决于要存储的地址类型
3.指针所占存储空间大小取决地址大小,地址大小与元素类型有关系吗?是没有关系的,都是64bit地址。所以指针都是 8bytes与类型无关
4.不同类型的指针有什么区别?运算能力不同
char *p; int *q;
ii.sizeof(p)==sizeof(q);都是8bytes
iii.但是p+1!=q+1;
iv.p+1偏移地址是1bytes
v.q+1偏移地址是4bytes
5.作用
i.形参改变实参
ii.参数的回填
6.指针的运算
++和 *
7.const修饰指针
指针常量:char *const p;
什么是指针常量?
就是指针本身是一个常量,也就是说,指针本身指向的内存地址(存储空间)不能改变。不过,内存地址中存储的值可以发生改变。
也就是p不能改变,*p可以改变
常量指针:
const char *p; 或 char const *p;
什么是常量指针?
就是指向一个常量的内存地址的指针。(指针本身不是常量)也就是说,指针指向的内存地址可以发生改变。但是目前指针指向的内存地址中的值(也就是那个常量)不能改变。
8.存储字符串
字符串变量char sl]="hello";
字符串常量char*p="hello";p存储的不是字符串,是字符串的起始地址
9.类型
1.变量的指针
2.数组的指针
int (*p)[10];
3.函数的指针
char *(*p)(char *, const char *); //p就是函数指针
int (*q)(int , int ); //q就是函数指针
4.万能指针
1. void*
2.没有步长不能+、-、*等运算
五、结构体、共用体、枚举
1.结构体struct
struct stu_st {
int age;
char name[32];
float score;
};
注意结构体的对齐规则
结构体内可以有结构体类型,也可以包含同类型的结构体指针,但是不能包含同类型的变量
struct stu_st {
int age;
//struct stu_st st; 错误的
struct stu_st *prev, *next;允许的
};
变长结构体
结构体中最后一个元素是一个有1个成员的数组
开辟存储空间的时候,连同变长的存储空间共同开辟
有一个成员的数组的数组名就是可变长的存储空间的起始地址
struct test_st{ struct test_st *prev, *next; char data[0];//如果编译器不支持写0那么就写1 }; malloc(sizeof(struct test_st) + size);//size就是data中存储的数据大小
2.共用体union
注意共用体的对齐规则,存储空间大小按照成员的最大值
共用体的大小端存储问题
大端存储
一个数据的高字节存低地址,低字节存高地址
小端存储
数据的高字节存高地址,低字节存低地址
3.枚举enum
枚举的都是常量值
enum {MONDAY, TUESDAY, WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY};
默认情况下,枚举的常量值是从0开始的整数序列,也就是MONDAY == 0, TUESDAY == 1
//也可以设置值
enum {MONDAY = 1, TUESDAY, WEDNESDAY,THURSDAY = 9,FRIDAY,SATURDAY,SUNDAY};
/*
MONDAY == 1
TUESDAY == 2
WEDNESDAY == 3
THURSDAY == 9
FRIDAY == 10
SATURDAY == 11
SUNDAY == 12
*/