【C语言】 C 语言 关键字分析 ( 属性关键字 | 常量关键字 | 结构体关键字 | 联合体关键字 | 枚举关键字 | 命名关键字 | 杂项关键字)

142 篇文章 96 订阅

相关文章链接 :
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 在栈内存中 ] )


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关键字说明 ( 声明寄存器存储 ( 不确定 ) | 适用前提 ① 值符合CPU要求 ② 不能用 & 取地址 , & 只能取内存地址 不能取 CPU 地址 )


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", &register_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", &register_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* 指针强转为被赋值的类型 )


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 关键字说明 ( 声明外部文件的 变量 和 函数 | 设置编译方式 C++ 中 命令编译器 以 标准 C 规范编译 变量 和 函数 )


extern 关键字说明 :

  • 1.主要作用 : 声明外部文件中定义的 变量 和 函数;
  • 2.设置编译方式 : 有些 C ++ 编译器 和 一些 变种 C 编译器 编译变量 和 函数时有时不遵守标准C 规范, 通过 extern 关键字可以***命令编译器以 标准C 规范编译 变量和函数***.
extern "C"
{
	...
}


(2) extern 引用外部文件示例 ( 声明外部变量 : extern 类型 变量名称; | 声明外部函数 : 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_variableint666

  • 3.编译器左值判定 : 编译器查找const常量是否有左值, 如果有报错;
  • 4.编译器右值判定 : 编译器查找 const 常量是否当做右值,如果出现 int a = const_variable, 那么直接将 const_variable 替换为 666; 如果出现 int p = (int) &const_variable, 那么不做任何操作;

const 修饰数组 :

  • 1.只读数组 : const 修饰数组时, 这个数组是只读的, 数组中的每个元素都是只读的, 不能作为左值;
  • 2.const 数组所在空间不可改变 : 数组所在的空间是不可更改的, 但是通过指针是可以修改数组中每个元素的值的;

const 修饰指针 : 需要符合下面的规则 :

声明特征
const int* pp指针地址可变 p指针指向的内容不可变 (const 在 * 左边, 数据不可变)
int const* pp指针地址可变 p指针指向的内容不可变 (const 在 * 左边, 数据不可变)
int* const pp指针地址不可变 p指针指向的内容不可变 (const 在 * 右边, 地址不可变)
const int* const pp指针地址不可变 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 关键字 代码示例 : 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 结构体名称 {}; | ② 结构体变量声明 : struct 结构体名 变量名 ; | ③ 定义结构体同时声明结构体变量 : struct 结构体名称 {} 变量名 ; | ④ 结构体定义别名 : typedef struct 结构体名称 {} 别名名称; 声明变量 : 别名名称 变量名称 ; [ 定义一个别名后, ] | ⑤ 结构体定义别名省略结构体名称 : typedef struct {} 别名名称; 声明变量 : 别名名称 变量名称 ; )


结构体定义 :

  • 1.结构体定义最标准方法 : 最基本的方法, 声明一个结构体, 使用 struct 结构体名 变量名 来声明结构体变量;
//定义结构体
struct Student           
{
    char* name;
    int age;
};

//声明结构体变量
struct Student stu;
  • 2.定义结构体同时声明结构体变量 : 在定义结构体的大括号后 写上变量名;
//定义结构体的同时 声明结构体变量
struct Student           
{
    char* name;
    int age;
}stu;
  • 3.定义结构体同时声明变量 但不定义结构体类型名称 : 定义结构体时如果不给出结构体的名称, 那么只能在定义结构体时 同时 声明该变量, 该结构体只有一个变量;
//定义结构体的同时 声明结构体变量, 如果不给出结构体类型名称, 在别处不能声明该类型的结构体变量
struct         
{
    char* name;
    int age;
}stu;
  • 4.结构体定义别名 : 定义了别名的结构体, 在声明结构体变量时可以不适用 struct 关键字;
//定义结构体类型, 并给 _student 定义一个别名 student
typedef struct _student        
{
    char* name;
    int age;
}student;

//声明结构体变量
student stu;

-5.定义结构体别名 但是没有给出结构体的类型名称 : 如果使用 typedef 定义了一个结构体的别名, 那么该结构体可以不定义结构体类型名称;

//定义结构体类型, 并定义一个别名 student, 可以不定义结构体的类型名称
typedef struct      
{
    char* name;
    int age;
}student;

//声明结构体变量
student stu;


(2) 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.



(3) 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.执行结果 :

这里写图片描述



(4) 柔性数组 代码示例 ( 处理斐波那契数列 )


柔性数组使用 :

  • 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 只为最大的元素分配内存空间 所有元素共享该空间 )


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 枚举关键介绍 ( 定义常量 | 只能定义 int 类型 )


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 区别 ( #define 编译时进行替换 | enum 是常量值 )


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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值