相关文章链接 :
1.【嵌入式开发】C语言 指针数组 多维数组
2.【嵌入式开发】C语言 命令行参数 函数指针 gdb调试
3.【嵌入式开发】C语言 结构体相关 的 函数 指针 数组
4.【嵌入式开发】gcc 学习笔记(一) - 编译C程序 及 编译过程
5.【C语言】 C 语言 关键字分析 ( 属性关键字 | 常量关键字 | 结构体关键字 | 联合体关键字 | 枚举关键字 | 命名关键字 | 杂项关键字)
一. 属性关键字 (auto | static | register)
每个C语言变量都有自己的属性.
定义变量时可以在变量前加上 “属性关键字” 来为变量定义属性.
auto 关键字 : auto 是C语言变量的默认属性, 所有的局部变量都被编译器默认为 auto 属性. 存储在栈中.
static 关键字 : 声明静态, 限定作用域. 存储在静态存储区.
register关键字 : 声明存储在 CPU 寄存器中. 存储在CPU寄存器中.
1. auto 关键字
(1) auto 关键字说明
auto 关键字 :
- 1.C语言默认属性 : 如果一个变量前没有写明属性, 那就是默认为 auto 的属性;
- 2.声明栈存储 : 使用auto修饰的变量, 会默认存储在程序的栈中.
- 3.只能修饰局部变量 : auto 关键字只能修饰局部变量, 修饰全局变量编译时会报错.
auto不能修饰全局变量 : auto 关键字不能修饰全局变量, 因为 auto 修饰的变量存储在栈内存中, 全局变量存储在全局区, 此时出现了冲突. 如果使用auto修饰全局变量, 编译时会报错.
存储类型说明 : C 语言中的变量存储 由上到下顺序 : 栈区(stack) -> 堆区(heap) -> 全局区 -> 字符常量区 -> 代码区
(2) auto 关键字 代码示例 ( 不能修饰全局变量 | 错误示例 )
auto 关键字只能修饰局部变量, 修饰全局变量会报错 :
- 1.代码 : test_1.c .
#include<stdio.h>
//使用auto修饰全局变量,编译时直接报错,因为auto代表存储在栈中, 全局变量存储在全局区, 因此auto只能修饰局部变量, 这里出现错误, 直接注释掉.
auto int global_auto = 0;
-
2.编译结果 : 提示全局变量不能使用 auto 关键字声明.
(3) auto 关键代码示例 ( 正确用法 )
正确使用 auto 关键字 :
- 1.代码 :
#include<stdio.h>
//使用auto修饰全局变量,编译时直接报错,因为auto代表存储在栈中, 全局变量存储在全局区, 因此auto只能修饰局部变量, 这里出现错误, 直接注释掉.
//auto int global_auto = 0;
int global_auto = 0;//该声明合法
int main()
{
//auto 关键字只能修饰局部变量, 不能修饰全局变量
auto int auto_variable = 0;
//获取局部变量的地址,该地址是栈内存地址
printf("%0X\n", &auto_variable);
return 0;
}
-
2.执行结果 : 打印出了栈内存中的地址.
2. static 关键字
(1) static 关键字说明
static 关键字两大作用 :
- 1.静态属性 : static 修饰局部变量 指明变量是静态的, 该变量存储在程序静态区.
- 2.作用域限定符 : static 作为 文件作用域限定符.
static 修饰局部变量(声明静态存储区) :
- 1.作用 : 说明该局部变量存储在静态存储区.
- 2.初始化次数 : 该值只会初始化一次, 之后会被不断赋值, 调用该局部变量所在方法, 每次的值都是上次调用该方法计算完毕后的值. 如果是第一次调用, 那么就初始化这唯一的一次.
- 3.声明周期 : 该局部变量的生命周期从第一次初始化直到程序退出为止.
static 修饰全局变量和函数(声明作用域) :
- 1.修饰全局变量 : static 如果修饰全局变量, 那么就说明该全局变量只能在本文件中使用, 其它文件无法访问.
- 2.修饰函数 : static 如果修饰函数, 那么该函数只能
存储类型说明 : C 语言中的变量存储 由上到下顺序 : 栈区(stack) -> 堆区(heap) -> 全局区 -> 字符常量区 -> 代码区
(2) static 关键字 代码示例 ( 修饰局部变量 )
static 关键字修饰局部变量, 只初始化一次, 之后每次使用, 都不再进行初始化操作.
static 修饰局部变量示例 : 两个方法中各有一个变量, 一个静态, 一个auto, 分别调用5次方法,进行对比.
- 1.代码 :
#include<stdio.h>
//定义两个函数, 区别是一个是 auto 变量, 一个是 static 变量, 本函数定义是 auto 变量
void method1()
{
//每次调用都调用该值
int local_variable_auto = 0;
local_variable_auto ++;
printf("%d\n", local_variable_auto);
}
//定义两个函数, 区别是一个是 auto 变量, 一个是 static 变量, 本函数定义是 static 变量
void method2()
{
//与method1对比就是局部变量使用 static 修饰
//该变量只初始化一次, 之后调用, 获取的值都是上次用完后的值, 即使被赋值很多次, 获取到的值是最后一次赋值的值.
static int local_variable_static = 0;
local_variable_static ++;
printf("%d\n", local_variable_static);
}
int main()
{
//C编译器中可以不声明, 默认局部变量时 auto 属性的.
auto int i = 0;
//调用五次定义了auto局部变量的值,其中的局部变量每次都初始化
for(i = 0; i < 5; i ++){
method1();
}
//调用五次定义了static局部变量的值,其中的静态变量只初始化一次,之后每次都用上一次赋值过的变量
for(i = 0; i < 5; i ++){
method2();
}
return 0;
}
-
2.执行结果 :
分析 :
调用5次method1()方法, 每次local_variable_auto 变量都初始化.
调用5次method2()方法, local_variable_static 变量只初始化一次, 之后每次都沿用上一次的值.
(3) static 关键字 代码示例 ( 限定变量和方法 作用域 )
static 关键字 限定变量 只能在本代码中访问被修饰的变量和函数 :
- 1.代码1 : 主程序 test_1.c ;
#include<stdio.h>
//引用test_2.c 文件中的普通全局变量,该声明合法.
extern int test_2_global;
//引用test_2.c 中的静态全局变量, 在使用时会报错.
//extern int test_2_global_static;
//引用test_2.c 中的普通函数, 通过该普通函数可以获取test_2.c 中的 test_2_global_static 静态变量
extern int method_3();
//引用test_2.c 中的静态函数, 使用时会报错.
//extern int method_4();
//引用test_2.c 中的普通函数, 通过该普通函数可以调用test_2.c 中的method_4() 静态函数
extern int method_5();
int main()
{
//打印 test_2.c 中的全局变量,查看是否在本文件中引用成功.
printf("%d\n", test_2_global);
//打印 test_2.c 中的静态全局变量, 此时编译时会报错,这里注释掉.
//printf("%d\n", test_2_global_static);
//通过调用 method_3() 获取 test_2.c 中的静态全局变量, 打印出该静态全局变量的值
printf("%d\n", method_3());
//无法调用 test_2.c 中的静态方法, 编译时会报错.
//printf("%d\n", method_4());
//通过调用 method_5, 间接调用 test_2.c 中的 method_4() 静态函数, 获取该静态函数的值并打印出来.
printf("%d\n", method_5());
return 0;
}
- 2.代码2 : 外部文件 test_2.c ;
//普通的全局变量, 其它文件可以引用该变量
int test_2_global = 666;
//静态全局变量, 同时限定其作用域是本文件, 不能被外部文件使用.
static int test_2_global_static = 444;
//通过调用该方法, 可以在外部文件访问该方法, 获取静态全局变量的值.
int method_3()
{
return test_2_global_static;
}
//使用static修饰该方法, 外部文件无法使用该方法.
static int method_4()
{
return test_2_global_static;
}
//在普通方法中调用static修饰的方法, 此时可以在外部文件中访问该普通方法, 即通过普通方法调用 static 方法.
int method_5()
{
return method_4();
}
-
3.执行结果 : 需要同时编译两个文件 gcc test_1.c test_2.c ;
3. register 关键字
(1) register关键字说明
register 关键字说明 :
- 1.作用 : 声明变量存储的位置是在 寄存器 中.
- 2.成功率 : 该关键字只是请求编译器将该变量存储在寄存器中, 编译器不一定会批准.
- 3.不能修饰全局变量 : register 修饰全局变量会报错, 因为全局变量声明周期是整个程序的声明周期,该周期内长时间占用 CPU 寄存器明显不可能, 因此编译器禁止register修饰全局变量.
register 使用前提 :
- 1.值符合要求 : 该变量的值必须能够被 CPU 的寄存器接受.
- 2.无法获取地址 : 取地址运算符 & 不能获取 register 变量地址, & 只是能获取内存地址, 不能获取 CPU 地址.
使用情况 : 当需求是实时性要求效率非常高时, 就应该使用寄存器变量.
(2) register 关键字代码示例 ( 不能修饰全局变量 | 错误示例 )
register 不能修饰全局变量 : CPU 寄存器内的变量其生命周期不能太长.
- 1.代码 : test_1.c ;
#include<stdio.h>
//使用register 修饰全局变量,此时编译也会报错,全局变量声明周期是整个程序的生命周期,如果将其放入CPU寄存器,
//会导致寄存器占用, 因此编译器规定寄存器变量不能是全局变量. 这里将错误代码注释掉.
register int global_register = 0;
-
2.执行结果 : register修饰全局变量报错;
(3) register 关键字代码示例 ( 不能获取register变量地址 | 错误示例 )
register 变量无法获取地址 :
- 1.代码1 : test_1.c .
#include<stdio.h>
int main()
{
//register 关键字只能修饰局部变量,不能修饰全局变量
register int register_variable = 0;
//尝试获取CPU寄存器的地址, 此时编译时会报错. 这里注释掉.
printf("%0x\n", ®ister_variable);
return 0;
}
-
2.执行结果 :
4. 属性关键字综合示例
属性关键字综合示例 :
- 1.代码1 : test_1.c;
#include<stdio.h>
//使用auto修饰全局变量,编译时直接报错,因为auto代表存储在栈中, 全局变量存储在全局区, 因此auto只能修饰局部变量, 这里出现错误, 直接注释掉.
//auto int global_auto = 0;
int global_auto = 0;//该声明合法
//使用register 修饰全局变量,此时编译也会报错,全局变量声明周期是整个程序的生命周期,如果将其放入CPU寄存器,
//会导致寄存器占用, 因此编译器规定寄存器变量不能是全局变量. 这里将错误代码注释掉.
//register int global_register = 0;
int global_register = 0;//该声明合法
//定义两个函数, 区别是一个是 auto 变量, 一个是 static 变量, 本函数定义是 auto 变量
void method1()
{
//每次调用都调用该值
int local_variable_auto = 0;
local_variable_auto ++;
printf("%d\n", local_variable_auto);
}
//定义两个函数, 区别是一个是 auto 变量, 一个是 static 变量, 本函数定义是 static 变量
void method2()
{
//与method1对比就是局部变量使用 static 修饰
//该变量只初始化一次, 之后调用, 获取的值都是上次用完后的值, 即使被赋值很多次, 获取到的值是最后一次赋值的值.
static int local_variable_static = 0;
local_variable_static ++;
printf("%d\n", local_variable_static);
}
//引用test_2.c 文件中的普通全局变量,该声明合法.
extern int test_2_global;
//引用test_2.c 中的静态全局变量, 在使用时会报错.
//extern int test_2_global_static;
//引用test_2.c 中的普通函数, 通过该普通函数可以获取test_2.c 中的 test_2_global_static 静态变量
extern int method_3();
//引用test_2.c 中的静态函数, 使用时会报错.
//extern int method_4();
//引用test_2.c 中的普通函数, 通过该普通函数可以调用test_2.c 中的method_4() 静态函数
extern int method_5();
int main()
{
//auto 关键字只能修饰局部变量, 不能修饰全局变量
auto int auto_variable = 0;
//static 既可以修饰局部变量(声明存储于静态存储区), 又可以修饰全局变量(文件作用域限定)
static int static_variable = 0;
//register 关键字只能修饰局部变量,不能修饰全局变量
register int register_variable = 0;
//获取局部变量的地址,该地址是栈内存地址
printf("%0x\n", &auto_variable);
//获取静态变量的地址, 该地址是静态区地址
printf("%0x\n", &static_variable);
//尝试获取CPU寄存器的地址, 此时编译时会报错. 这里注释掉.
//printf("%0x\n", ®ister_variable);
//C编译器中可以不声明, 默认局部变量时 auto 属性的.
auto int i = 0;
//调用五次定义了auto局部变量的值,其中的局部变量每次都初始化
for(i = 0; i < 5; i ++){
method1();
}
//调用五次定义了static局部变量的值,其中的静态变量只初始化一次,之后每次都用上一次赋值过的变量
for(i = 0; i < 5; i ++){
method2();
}
//打印 test_2.c 中的全局变量,查看是否在本文件中引用成功.
printf("%d\n", test_2_global);
//打印 test_2.c 中的静态全局变量, 此时编译时会报错,这里注释掉.
//printf("%d\n", test_2_global_static);
//通过调用 method_3() 获取 test_2.c 中的静态全局变量, 打印出该静态全局变量的值
printf("%d\n", method_3());
//无法调用 test_2.c 中的静态方法, 编译时会报错.
//printf("%d\n", method_4());
//通过调用 method_5, 间接调用 test_2.c 中的 method_4() 静态函数, 获取该静态函数的值并打印出来.
printf("%d\n", method_5());
return 0;
}
- 2.代码2 : test_2.c ;
//普通的全局变量, 其它文件可以引用该变量
int test_2_global = 666;
//静态全局变量, 同时限定其作用域是本文件, 不能被外部文件使用.
static int test_2_global_static = 444;
//通过调用该方法, 可以在外部文件访问该方法, 获取静态全局变量的值.
int method_3()
{
return test_2_global_static;
}
//使用static修饰该方法, 外部文件无法使用该方法.
static int method_4()
{
return test_2_global_static;
}
//在普通方法中调用static修饰的方法, 此时可以在外部文件中访问该普通方法, 即通过普通方法调用 static 方法.
int method_5()
{
return method_4();
}
-
3.执行结果 :
二. 其它关键字 ( goto | void | extern | sizeof)
1. goto 关键字
goto 现状 (建议禁用) : 一般不使用 goto;
- 1.对程序质量影响 : 程序中使用的 goto 越多, 代码质量越垃圾;
- 2.禁用 goto : goto 对代码质量产生恶劣影响, 高手写代码一般不使用 goto;
- 3.后续高级语言删除了 goto : 后续的 Java 等高级语言中, 没有 goto 关键字;
- 4.原因 : 破坏了 过程式 程序顺序执行 的规则;
2. void 关键字
(1) void 关键字说明
void 关键字作用 : 修饰函数 返回值 和 参数;
- 1.修饰返回值 : 函数 没有返回值, 其类型就应该写成 void;
- 2.修饰参数 : 如果函数 没有参数, 即不接收参数, 其参数类型就写成 void;
-
3.本质 : 使用 void 修饰 返回值 和 参数, 其本质就代表没有;
void 类型大小C语言规范没有规定, C 语言中是没有 void 变量的. 使用sizeof查看void大小, gcc 返回1 这是编译器厂商给的一个值, 不是C语言中规定的.
void 不能修饰变量, 否则会报错.
(2) void * 指针介绍
void * 指针说明 :
- 1.被赋值的情况(作为左值) : void * 指针作为被赋值对象, 即在 “=” 左侧, 其可以直接被赋值为任何指针类型变量;
- 2.赋值给其它指针(作为右值) : void * 赋值给其它类型的指针变量, 需要强制转换为其它类型的指针变量.
(3) void * 指针 代码示例 ( 实现 memset 方法 )
使用 void * 指针实现 memset 方法示例 :
- 1.代码示例 : test_1.c ;
#include<stdio.h>
//实现 memset 函数, 下面是memset函数的内容.
//void *memset(void *s, int ch, size_t n);
//函数解析:将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s
//memset:作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法
//实现方法 :
//1.接收参数 : void *s 内存地址, int ch 每个字节赋值内容, int size 替换的字节个数.
//2.算法实现 : for 循环实现设置
//3.返回值 : void *
void * memset(void *p, char v, int size)
{
//接收一个任意的内存地址值(任意类型的指针变量)
void * ret = p;
//将 void * 强制转换为 char *
char * dest = (char*)p;
int i = 0;
for(i = 0; i < size; i ++){
//使用 char 类型指针, 依次将之后的字节每个设置成 v
dest[i] = v;
}
return ret;
}
int main()
{
//定义一个数组, 之后我们将使用自定义的memset方法重置数组中的内容
int array[5] = {1, 2, 3, 4, 5};
//循环控制变量
int i = 0;
//打印数组的原始值
for(i = 0; i < 5; i ++){
printf("%d\n", array[i]);
}
//调用自己定义的memset函数清空数组内容
memset(array, 0, sizeof(array));
//打印数组经过memset重置后的数值
for(i = 0; i < 5; i ++){
printf("%d\n", array[i]);
}
//定义long类型测试
long l = 66666;
printf("%ld\n", l); //打印值
memset(&l, 0, sizeof(l)); //重置long变量
printf("%ld\n", l); //再次打印重置后的值
return 0;
}
-
2.执行结果 :
3. extern 关键字
(1) extern 关键字说明
extern 关键字说明 :
- 1.主要作用 : 声明外部文件中定义的 变量 和 函数;
- 2.设置编译方式 : 有些 C ++ 编译器 和 一些 变种 C 编译器 编译变量 和 函数时有时不遵守标准C 规范, 通过 extern 关键字可以命令编译器以 标准C 规范编译 变量和函数.
extern "C"
{
...
}
(2) extern 引用外部文件示例
extern 引用外部文件 :
- 1.代码示例1 : test_1.c ;
#include <stdio.h>
//引用外部文件 test_2.c 中定义的全局变量
extern int test_2_a;
//引用外部文件 test_2.c 中定义的方法
extern int test_2_get_min(int a, int b);
int main()
{
//调用外部变量 test_2_a, 并打印出其值
printf("%d\n", test_2_a);
//调用外部函数 test_2_get_min , 并打印结果
printf("%d\n", test_2_get_min(666, 888));
return 0;
}
- 2.代码示例2 : test_2.c ;
//test_2.c 中定义的全局变量
int test_2_a = 666;
//test_2.c 中定义的函数
int test_2_get_min(int a, int b)
{
//返回a和b中较小的值
return (a < b) ? a : b;
}
-
3.执行结果 :
(3) extern 关键字代码示例 ( 编译方式 )
extern 指定编译方式代码示例 :
- 1.代码示例 : test_1.c ;
#include <stdio.h>
//如果在 gcc 编译器中, 该用法直接报错
//在 g++ 编译器中, 该用法有效
extern "C"
{
int c_get_min(int a, int b)
{
//返回a和b中较小的值
return (a < b) ? a : b;
}
}
int main()
{
//g++ 编译器中编译通过即可执行
printf("%d\n", c_get_min(666, 888));
return 0;
}
-
2.执行结果 :
4. sizeof 关键字
(1) sizeof 关键字说明
sizeof 关键字说明 :
- 1.sizeof 本质 : sizeof 不是函数, 其本质是一个编译器的内置的指示符;
- 2.sizeof 确定值的时机 : sizeof 的实际值值, 在编译的过程中就已经知道了结果.
- 3.sizeof 作用 : 计算变量或者类型 等实体 占用内存的大小.
(2) sizeof 关键字 代码示例 ( 使用方法 )
sizeof 关键字 代码示例 :
- 1.代码示例 :
#include <stdio.h>
int main()
{
int a;
//这种sizeof, 将变量放在括号里, 与函数调用类似
printf("%ld\n", sizeof(a));
//可以不写括号, 也可以打印出 变量 a 的大小, 注意 类型 不能这么写
//只有变量可以这么写
printf("%ld\n", sizeof a);
//可以传入类型 int, 打印出 int 类型占用内存大小
printf("%ld\n", sizeof(int));
return 0;
}
-
2.执行结果 :
三. 常量 和 易变 关键字 ( const | volatile )
1. const 关键字 简介
(1) const 关键字 简介
const 关键字 说明 :
- 1.const 常量本质 : const 不是真的常量, 也是一种变量.
- 2.修饰只读变量 : const 修饰的变量智能读取, 不能被赋值, 即不能当做左值;
- 3.占用内存 : const 变量也会占用内存中的空间 ;
- 4.修改const值 : 使用指针可以修改const变量地址中的值.
const 只是针对编译器是有用的, 在运行的时候 就没有这种约束了, 可以改变其值.
编译器处理 const 常量过程 :
- 1.定义 const 常量 : const int const_variable = 666, 之后编译器开始编译;
- 2.生成符号表 : 编译器生成一个符号表, const_variable 对应 int 类型, 值为 666;
常量标示符 | 常量类型 | 常量值 |
---|---|---|
const_variable | int | 666 |
- 3.编译器左值判定 : 编译器查找const常量是否有左值, 如果有报错;
- 4.编译器右值判定 : 编译器查找 const 常量是否当做右值,如果出现 int a = const_variable, 那么直接将 const_variable 替换为 666; 如果出现 int p = (int) &const_variable, 那么不做任何操作;
const 修饰数组 :
- 1.只读数组 : const 修饰数组时, 这个数组是只读的, 数组中的每个元素都是只读的, 不能作为左值;
- 2.const 数组所在空间不可改变 : 数组所在的空间是不可更改的, 但是通过指针是可以修改数组中每个元素的值的;
const 修饰指针 : 需要符合下面的规则 :
声明 | 特征 |
---|---|
const int* p | p指针地址可变 p指针指向的内容不可变 (const 在 * 左边, 数据不可变) |
int const* p | p指针地址可变 p指针指向的内容不可变 (const 在 * 左边, 数据不可变) |
int* const p | p指针地址不可变 p指针指向的内容不可变 (const 在 * 右边, 地址不可变) |
const int* const p | p指针地址不可变 p指针指向的内容不可变 (const 在 * 左边 和 右边, 数据和地址都不可变) |
const 修饰指针规则 : 左数 右指 (左边数据是常量, 右边指针是常量);
左数 : const 出现在 左边时, 指针指向的数据为常量*, 指向的数据不可改变;
右指 : const 出现在 右边时, 指针地址本身是常量*, 指针地址不可改变;
const 修饰函数参数 和 返回值 :
- 1.const 修饰参数 : 在函数体内, 不希望改变参数的值;
- 2.const 修饰返回值 : 一般情况下 返回值 使用 const 修饰, 是返回指针, 用于限制 指针指向的内容不允许改变 ;
(2) const 关键字 代码示例 ( const 常量不能被赋值 | 错误示例)
const 关键字 代码示例 : const 常量不能被赋值.
- 1.代码示例 :
#include <stdio.h>
int main()
{
//定义一个 const 变量, 直接赋值编译器报错, 但是使用指针改变该地址的值是可以的.
const int const_variable = 666;
printf("%d\n", const_variable);
//const 变量不能被直接赋值, 这样在编译的时候就会报错
const_variable = 444;
printf("%d\n", const_variable);
return 0;
}
-
2.执行结果 :
const 常量一旦作为左值, 编译时就会报错
(3) const 关键字 代码示例 ( 通过指针修改const常量 )
const 关键字 代码示例 : const 常量可以通过指针修改
- 1.代码示例 :
#include <stdio.h>
int main()
{
//定义一个 const 变量, 直接赋值编译器报错, 但是使用指针改变该地址的值是可以的.
const int const_variable = 666;
printf("%d\n", const_variable);
//const 变量不能被直接赋值, 这样在编译的时候就会报错, 这里注释掉
//const_variable = 444;
//获取 const 变量的地址, 并改变该地址的值
int* p = (int*) &const_variable;
*p = 888;
printf("%d\n", const_variable);
return 0;
}
-
2.执行结果 :
通过指针可以修改 const 常量
(4) const 关键字 代码示例 ( 修饰指针 | 错误示例 )
const 修饰指针规则 : 左数右指;
左数 : const 出现在 左边时, 指针指向的数据为常量*, 指向的数据不可改变;
右指 : const 出现在 右边时, 指针地址本身是常量*, 指针地址不可改变;
const 关键字 代码示例 : 修饰指针
- 1.代码示例1 : const 出现在 * 左边, const int* p = &i;
#include <stdio.h>
int main()
{
//定义普通的变量, 用于取地址用
int i = 666;
//定义一个 const 在 * 左边的例子, 意义是 指针指向的内容是常量
//按照规则, 指针地址可改变, 指针指向的数据不可变
const int* p = &i;
//指针指向的数据不可改变, 这里会报错
*p = 444;
return 0;
}
- 2.代码示例2 : const 出现在 * 左边, int const* p = &i;
#include <stdio.h>
int main()
{
//定义普通的变量, 用于取地址用
int i = 666;
//定义一个 const 在 * 左边的例子, 意义是 指针指向的内容是常量
//按照规则, 指针地址可改变, 指针指向的数据不可变
int const* p = &i;
//指针指向的数据不可改变, 这里会报错
*p = 444;
return 0;
}
- 3.代码示例3 : const 出现在 * 右边, int* const p = &i;
#include <stdio.h>
int main()
{
//定义普通的变量, 用于取地址用
int i = 666;
//定义一个 const 在 * 右边的例子, 意思是 地址是常量
//按照规则, 指针地址不可改变, 指针指向的内容可变
int* const p = &i;
//指针指向的数据不可改变, 这里会报错
p = NULL;
return 0;
}
- 4.代码示例4 : const 同时出现在 * 左边 和 右边, const int* const p = &i;
#include <stdio.h>
int main()
{
//定义普通的变量, 用于取地址用
int i = 666;
//定义 const 同时出现在 * 左边 和 右边, 则指针的地址 和 指向的数据都不可改变
const int* const p = &i;
//下面的两个操作, 一个是想修改指针地址, 一个是想修改指针值, 这两个都报错.
p = NULL;
*p = 444;
return 0;
}
(5) const 关键字 代码示例 ( 修饰返回值 )
const 关键字 代码示例 : const 修饰返回值
- 1.代码示例 :
#include <stdio.h>
//返回的指针 使用 const 修饰,
//const 在 指针* 的左边, 即其指向的内容是常量, 不可更改
const int* function()
{
//声明静态局部变量,该变量只初始化一次, 之后重复使用
static int count = 0;
count ++;
return &count;
}
int main()
{
//使用 const int* 类型才可以接收 返回值是 const int* 类型的返回值
//如果没有 const 修饰, 会报警告
const int* p = function();
printf("%d\n", *p);
return 0;
}
-
2.执行结果 :
2. volatile 关键字 简介
(1) volatile 关键字 简介
volatile 关键字简介 :
- 1.作用 : 编译器 警告指示, 告诉编译器 每次去内存中取变量值 , 防止编译器自己做优化, 改变了程序的执行逻辑;
- 2.使用情况 : ① 一个变量可能被多个变量同时访问的情况, ② 变量可能被未知因素更改的情况 ;
(2) volatile 关键字 代码示例
编译器优化案例 : 有时我们不需要编译器的优化, 有时编译器优化完了反而得不到我们想要的执行效果 ;
- 代码示例说明 :
#include <stdio.h>
#include <unistd.h>
int main()
{
//value 全程没有做过左值, 值当做右值为 a 和 b 赋值用
//编译器会将 value 当做常量, 使用 666 替代 value
//如果使用 valatile 修饰value, 在编译的时候, 编译器就不会进行这种替换.
int value = 666;
//a 和 b 初始值为0
int a = 0;
int b = 0;
//编译器在编译时, 直接将 666 赋值给了 a
a = value;
sleep(1000);
//如果在休眠的 1000 ms,
//value内存值变成了 888, 我们想要的是888, 但是编译器自作主张将 b 赋值为了 666
//这样就无法实现我们想要的意图
b = value;
return 0;
}
四. 结构体 联合体 关键字 ( struct | union )
1. struct 关键字
(1) struct 结构体大小
空结构体占用内存大小 :
- 1.C规范定义 : C语言规范中没有定义空结构体的大小,不同编译器有不同的默认值0或者1字节;
- 2.代码示例 :
#include <stdio.h>
//定义一个空结构体,用来测试空结构体的大小
struct A
{
};
int main()
{
//定义两个空结构体变量,打印其大小和地址值
struct A a1;
struct A a2;
//打印空结构体类型大小
printf("%ld\n", sizeof(struct A));
//打印两个空结构体大小 和 空结构体变量地址
printf("%ld, %0X\n", sizeof(a1), &a1);
printf("%ld, %0X\n", sizeof(a2), &a2);
return 0;
}
-
3.执行结果 :
空结构体变量类型大小为0,其变量的大小为0,其地址错位1.
(2) struct 结构体实现柔性数组
柔性数组 :
- 1.普通数组 : 在定义的时候定义数组的大小,并且在栈上分配内存;
- 2.柔性数组 : 数组的大小未知,定义完之后可设置数组大小;
- 3.实现方式 : 使用结构体实现柔性数组.
- 4.代码示例 :
#include <stdio.h>
#include<stdlib.h>
//定义柔性数组
typedef struct soft_array
{
int len;
int array[];
} soft_array;
int main()
{
//打印 结构体 soft_array 的类型大小, 结果是4字节
//分析 : int array[] 是一个未知大小的数组, 编译器不知道该数组多大, 就将该数组大小当做0
//sizeof 计算的 结构体的 4 个字节是 int 类型的大小, 后面的 int array[] 只是占位符, 可以分配任意大小的数组
printf("%ld\n", sizeof(soft_array));
//为柔性数组分配内存空间, 结构体的基本空间 + 数组大小
soft_array* array = (soft_array*)malloc(sizeof(soft_array) + sizeof(int) * 10);
//设置柔性数组大小
array->len = 10;
int i = 0;
//以此遍历为柔性数组赋值
for(i = 0; i < array->len; i ++)
{
array->array[i] = i;
}
//依次遍历打印柔性数组的值
for(i = 0; i < array->len; i ++)
{
printf("%d\n", array->array[i]);
}
return 0;
}
- 5.执行结果 :
(3) 柔性数组 代码示例 ( 处理斐波那契数列 )
柔性数组使用 :
- 1.代码示例 :
#include <stdio.h>
#include<stdlib.h>
/*
柔性数组实现斐波那契数列
1. 定义柔性数组结构, typedef struct soft_array 实现;
2. 创建柔性数组, 编写一个创建函数 create_array() 方法;
3. 生成斐波那契数列, generate_array() 方法;
4. 释放柔性数组, delete_array() 方法.
*/
//1.定义柔性数组结构体
typedef struct soft_array
{
int len;
int array[];
} soft_array;
//2.创建柔性数组的函数
soft_array* create_array(int array_size)
{
soft_array* array = NULL;
//数组大小必须大于0
if(array_size > 0)
{
//从堆空间申请一个柔性数组内存空间
array = (soft_array*)malloc(sizeof(*array) + sizeof(*(array->array)) * array_size);
array->len = array_size;
}
return array;
}
//3.生成斐波那契数列并放入柔性数组
void generate_array(soft_array* array)
{
//传入的弹性数组不能为空
if(array != NULL)
{
//第一二项为1,后面第三项开始就是前两项之和
if(array->len == 1)
{
//数组大小就1个直接赋值1
array->array[0] = 1;
}
else if (array->len == 2)
{
//数组大小2个,这两个都赋值1
array->array[0] = 1;
array->array[1] = 1;
}
else
{
//如果超过2个,前两个赋值1,然后依次计算
array->array[0] = 1;
array->array[1] = 1;
int i = 0;
for(i = 2; i < array->len; i ++)
{
array->array[i] = array->array[i - 1] + array->array[i - 2];
}
}
}
}
//4.删除柔性数组
void delete_array(soft_array* array)
{
//释放内存空间
free(array);
}
int main()
{
//创建柔性数组, 为柔性数组分配内存空间, 结构体的基本空间 + 数组大小
soft_array* array = create_array(10);
//生成斐波那契数列
generate_array(array);
int i = 0;
//依次遍历打印柔性数组的值
for(i = 0; i < array->len; i ++)
{
printf("%d\n", array->array[i]);
}
//释放柔性数组
delete_array(array);
return 0;
}
-
2.执行结果 :
3. union 联合体 关键字
(1) struct 和 union 的区别
struct 和 union 的区别 :
- 1.struct 分配空间 : struct 结构体 为每个结构体元素分配独立的内存空间 ;
- 2.union 分配空间 : union 联合体 只为最大的联合体元素分配内存控件, 所有的元素共享该空间 ;
- 3.struct union 空间分配示例 :
#include <stdio.h>
//结构体需要为所有的元素分配内存空间
//该结构体占 8 个字节内存空间
struct A
{
int a;
int b;
};
//联合体只分配最大元素所占的内存空间
//该 联合体 占 4 字节 内存空间
union B
{
int a;
int b;
};
int main()
{
printf("%ld\n", sizeof(struct A));
printf("%ld\n", sizeof(union B));
return 0;
}
- 4.执行结果 :
(2) union 联合体注意事项
union 联合体 受大小端 影响 :
- 1.大端模式 : 低位数据放在 高位地址 中;
- 2.小端模式 : 低位数据放在 低位地址 中;
通过 union 判断系统的大小端 :
- 1.代码示例 :
#include <stdio.h>
//利用union联合体赋值受大小端影响的特性,
//判断当前的系统是大端模式还是小端模式
int big_or_small_check()
{
//定义联合体,其中定义int和char类型元素,两个元素共用一个内存空间
union check_end
{
int i;
char c;
} a;
//将大空间的int赋值为1
a.i = 1;
//四个字节赋值为1,如果是小端模式,那么高位地址都是0,最低位个字节是1
//char是占用最低位字节, 如果char 为1,说明就是小端模式
if(a.c == 1)
{
printf("small end\n");
}
else
{
printf("big end\n");
}
}
int main()
{
big_or_small_check();
return 0;
}
- 2.执行结果 :
五. 枚举 关键字 ( enum )
1. enum 关键字
(1) enum 枚举关键介绍
enum 简介 :
- 1.作用 : enum 是自定义类型, 作用是用来定义常量的 ;
- 2.定义要求 : enum 只能定义成 int 类型. 不能定义成 浮点型 ;
(2) enum 枚举代码示例
enum 枚举代码示例 :
- 1.代码示例 :
#include <stdio.h>
//enum 如果没有指明, 默认值是从 0 开始
//如果没有指定值, 那么后面一个默认是在前一个基础上加1
//如果显示的赋值后, 后面的类型依次加 1, 显示赋值之前的默认从 0 开始
//枚举是常量, 不能获取枚举的地址
enum color
{
RED,
YELLOW,
BLUE = 666,
GRAY
};
int main()
{
printf("%d\n", RED);
printf("%d\n", YELLOW);
printf("%d\n", BLUE);
printf("%d\n", GRAY);
return 0;
}
-
2.执行结果 :
(3) enum 和 define 区别
enum 与 define 区别 :
- 1.本质 : #define 是在编译的时候进行简单的替换, enum 枚举是常量值.
- 2.能否被调试 : #define 宏 无法调试, enum 可以;
- 3.类型 : #define 没有类型信息, enum 是 有特定的类型 的;
- 代码示例 :
#include <stdio.h>
#define LEN 888
int main()
{
//在编译的时候, #define 定义的 LEN 直接替换成 888 数字
int i = LEN;
int array[LEN];
return 0;
}
2. typedef 关键字
(1) typedef 关键字介绍
typedef 介绍 :
- 1.作用 : typedef 用于给 存在的数据类型 重新命名;
- 2.没有新类型 : typedef 关键字使用, 全程都没有产生 新的数据类型 ;
- 3.类型无法扩展 : 不能使用 unsigned 和 signed 等扩展 typedef 重命名的类型;
typedef 与 define 区别 :
- 1.typedef 本质 : typedef 是给已有类别重新命名;
- 2.define 本质 : define是简单的字符串替换, 没有类别 类型 等其它含义 ;
- 3.代码示例 :
#include <stdio.h>
typedef char* PCHAR
#define PINT int*
int main()
{
//这里 p1 和 p2 都是 char* 类型
PCHAR p1, p2;
//这里注意, 编译时PINT 会被替换成 int* p3, p4
//注意 *p3 是指针, p4 是 int 类型数据, int *p3, p4
PINT p3, p4;
return 0;
}