目录
2.5 基本数据类型—short、int、 long、char、float、double
2.15.1 const关键字也许应该被替换为readonly
2.21.1 历史的误会----也许应该是 typerename
引言:
C语言简洁、紧凑,使用方便、灵活。ANSI C标准C语言共有32个关键字,9种控制语句,程序书写形式自由,区分大小写。把高级语言的基本结构和语句与低级语言的实用性结合起来。 C 语言可以像汇编语言一样对位、字节和地址进行操作,而这三者是计算机最基本的工作单元。
简洁紧凑、灵活方便
ANSI C一共只有32个关键字,9种控制语句,程序书写形式自由,区分大小写。把高级语言的基本结构和语句与低级语言的实用性结合起来。 C 语言可以像汇编语言一样对位、字节和地址进行操作,而这三者是计算机最基本的工作单元。
C是结构式语言
结构式语言的显著特点是代码及数据的分隔化,即程序的各个部分除了必要的信息交流外彼此独立。这种结构化方式可使程序层次清晰,便于使用、维护以及调试。C语言是以函数形式提供给用户的,这些函数可方便的调用,并具有多种循环、条件语句控制程序流向,从而使程序完全结构化。
语法限制不太严格,程序设计自由度大:虽然C语言也是强类型语言,但它的语法比较灵活,允许程序编写者有较大的自由度。
允许直接访问内存地址,对硬件进行操作。由于C语言允许直接访问内存地址,可以直接对硬件进行操作,因此它既具有高级语言的功能,又具有低级语言的许多功能,能够像汇编语言一样对位、字节和地址进行操作,而这三者是计算机最基本的工作单元,可用来写系统软件。
生成目标代码质量高,程序执行效率高:一般只比经过高效优化的汇编程序生成的目标代码效率低10~20%。
适用范围大,可移植性好:C语言有一个突出的优点就是适合于多种操作系统,如DOS、UNIX、windows 98.windows NT;也适用于多种机型。C语言具有强大的绘图能力,可移植性好,并具备很强的数据处理能力,因此适于编写系统软件,三维,二维图形和动画,它也是数值计算的高级语言。
运算符丰富
C语言的运算符包含的范围很广泛,共有34种运算符。C语言把括号、赋值、强制类型转换等都作为运算符处理。从而使C语言的运算类型极其丰富,表达式类型多样化。灵活使用各种运算符可以实现在其它高级语言中难以实现的运算。
数据类型丰富
C语言的数据类型有:整型、实型、字符型、数组类型、指针类型、结构体类型、共用体类型等。能用来实现各种复杂的数据结构的运算。并引入了指针概念,使程序效率更高。另外C语言具有强大的图形功能,支持多种显示器和驱动器。且计算功能、逻辑判断功能强大。
同时对于不同的编译器也有各种强大的扩展功能。
另外C语言如此丰富数据类型及强大指针功能,其对硬件的管控能力极强,所以许多操作系统内核及MCU芯片程序开发都偏爱硬件。
1. 关于变量
1.1 什么是变量
1.2 变量的定义与声明
类型 变量名 = 默认值 //定义类型格式
char c = 'c'; //初始化
c = 'd'; //赋值
赋值的意思就是这个变量c已经存在了,存在的本质就是这个变量对应的空间已经开辟出来了 ,换言之就是将'd'字符直接放到一个已经存在的空间中。
1.3 为什么要定义变量
1.4 变量定义的本质
2. 关键字
C语言一共多少个关键字呢?一般的书上,都是32个(包括本书),但是这个都是 C90(C89) 的标准。其实 C99 后又新增了5个关键字。不过,目前主流的编译器,对 C99 支持的并不好,我们后面默认情况,使用 C90 ,即认为32个
关键字 | 说明 |
---|---|
auto | 声明自动变量 |
short | 声明短整型变量或函数 |
int | 声明整型变量或函数 |
long | 声明长整型变量或函数 |
float | 声明浮点型变量或函数 |
double | 声明双精度变量或函数 |
char | 声明字符型变量或函数 |
struct | 声明结构体变量或函数 |
union | 声明共用数据类型 |
enum | 声明枚举类型 |
typedef | 用以给数据取别名 |
const | 声明只读变量 |
unsigned | 声明无符号类型变量或函数 |
signed | 声明有符号类型变量或函数 |
extern | 声明变量是在其它文件正声明 |
register | 声明寄存器变量 |
static | 声明静态变量 |
volatile | 说明变量在程序执行中可被隐含地改变 |
void | 声明函数无返回值或无参数,声明无类型指针 |
if | 条件语句 |
else | 条件语句否定分支(与if连用) |
switch | 用于开关语句 |
case | 开关语句分支 |
for | 一种循环语句 |
do | 循环语句的循环体 |
while | 循环语句的循环条件 |
goto | 无条件跳转语句 |
continue | 结束当前循环,开始下一轮循环 |
break | 跳出当前循环 |
default | 开关语句中的“其他”分支 |
sizeof | 计算数据类型长度 |
return | 子程序返回语句(可以带参数,也可不带参数)循环条件 |
2.1 最宽宏大量的关键字 - auto
2.1.1 局部变量
包含在代码块中的变量叫做局部变量,局部变量具有临时性。
int x = 10;
if(x == 10)
{
int y = 20;
printf("code1: %d,%d",x,y);
}
printf("code2: %d,%d",x,y);
2.1.2 全局变量
在所有函数外定义的变量,叫做全局变量,全局变量具有全局性。
全局变量整个程序运行期间都有效,且在任何代码块中的都可以被访问,甚至是修改
下面程序输出的是局部,也就是局部和全部同名的时候,优先局部。
int g_val = 100;//全局变量
int main()
{
int g_val = 200;//局部变量
printf("%d",g_val);
}
//程序的打印结果为200
//局部优先
2.1.3 概念补充
2.1.4 auto相关
2.3 最快的关键字—register
2.3.1 计算机的分级存储
2.3.2 寄存器
我们可以不关系硬件细节,只要知道CPU内集成了一组存储硬件即可,这组硬件叫做寄存器
寄存器存在的本质:在硬件层面上,提高计算机的运算效率。因为不需要从内存里读取数据啦
我们知道了寄存器的作用之后再来看看寄存器变量—register吧
2.3.3 register 修饰变量
尽量将所修饰变量,放入CPU寄存区中,从而达到提高效率的目的
最后呢,这个关键字现在可以不用管,因为现在的编译器已经很智能了,能够进行比人更好的代码优化
2.4 最名不符实的关键字 - static
在讲这个关键字之前我们先看一个补充的内容
2.4.1 认识多文件
.h:我们称之为头文件,一般包含函数声明,变量声明,宏定义,头文件等内容(header)
.c: 我们称之为源文件,一般包含函数实现,变量定义等 (.c: c语言)
//test.h
#pragma once //防止头文件被重复包含,现在只需要记住,后面用的多
#include <stdio.h>
#include <windows.h>//包含了其他的windows头文件,调用函数时可以使用
//test.c
#include "test.h" //""里面包含头文件,目前只需要知道是自己写的头文件,就用""包含即可
//main.c
#include "test.h" //""里面包含头文件,目前只需要知道是自己写的头文件,就用""包含即可
int main()
{
printf("hello files!\n");
return 0;
}
简单看了上面的多文件调用之后,下面我们来看两个问题:
如果我的这个文件比较自私,不想把自己定义的变量和函数给别的文件用,那么有没有可能,我们不让全局变量或者函数跨文件访问,只在本文件内部被访问呢?
我们跨文件的时候总有些代码是不能够开源的,这时就出现了static
2.4.2 static相关
static void fun()
{
static int i = 0;
i++;
printf("i = %d\n", i);
}
int main()
{
for(int i = 0; i < 10; i++)
{
fun();
}
return 0;
}
/*不加static时打印结果应全为1
加上static后打印结果应为:
i = 1
i = 2
……
i = 10
2.5 基本数据类型—short、int、 long、char、float、double
2.6 变量命名
我们为什么要在关键字这里学变量命名呢?
- 命名是职业化的表现
- 面试官会让我们现场写代码,如果我们命名太随意,会表现得很业余,体现了我们的职业素养
2.7 最冤枉的关键字----sizeof
求特定一种类型,它对应开辟空间的大小的写法的过程中,下面哪些写法是正确的
int a = 10;
printf("%d\n",sizeof(a)); //1
printf("%d\n",sizeof(int)); //2
printf("%d\n",sizeof a); //3
printf("%d\n",sizeof int); //4
下面我们通过数组、指针、指针数组这三个概念来理解sizeof
2.8 signed、unsigned关键字
整型的存储
2.8.1 原码、反码、补码的补充
2.8.2 深入理解变量内容的存入和取出
在图中提到了空间是不关心内容的,那么这里的变量类型什么时候起效果呢?
变量存的过程:字面数据必须先转成补码,在放入空间当中。所以,所谓符号位,完全看数据本身是否携带+-号。和变量是否有符号无关!
变量取的过程:取数据一定要先看变量本身类型,然后才决定要不要看最高符号位。如果不需要,直接二进制转成十进制。如果需要,则需要转成原码,然后才能识别。(当然,最高符号位在哪里,又要明确大小端)
-10:1111 1111 1111 1111 1111 1111 1111 0110
4294967286:1111 1111 1111 1111 1111 1111 1111 0110
虽然打印出来转化后的二进制序列数值是一样的,但是十进制数值却不同,这就是由于最高位是否为符号位造成的差异
2.8.3 二进制快速转化口诀
2.8.4 大小端
如何理解大小端:
如何存取是由计算机、内存硬件厂商决定的,但是每个厂商都有不同的标准,由此产生了两种存储方案:
举个例子:
int a = 0x11223344
2.8.5 记忆口诀
低权值位 — 权值位比较小
低地址处 — 地址数字比较小
权值位比较小的放在地址数字比较小的地方,就叫做小端存储
所以我们可以用“小小小”来记忆小端存储,不然则为大端存储的
大小端是如何影响数据存储的
大小端存储方案,本质数据和空间按照字节为单位的一种映射关系
2.8.6 练习题
2.9 if、else组合
2.9.1 什么是语句
2.9.2 什么是表达式
2.9.3 基本语法
语法结构:
//1
if(表达式)
语句;
//2
if(表达式)
语句1;
else
语句2;
//3.多分支
if(表达式1)
语句1;
else if(表达式2)
语句2;
else
语句3;
//4.嵌套
if(表达式1){
语句1;
if(表示式x){
语句x;
}
else{
语句y;
}
}
else if(表达式2){
语句2;
}
else{
语句3;
}
2.9.4 if和else匹配问题
else 匹配采取就近原则
它总是与离他最近的if进行匹配
在书写代码的过程中尽量使用锯齿型,如上面的嵌套代码,就不会有匹配问题
2.10 _BOOL类型
2.10.1 什么是布尔类型
//测试代码
#include <stdio.h>
#include <stdbool.h> //布尔类型的头文件
int main()
{
bool ret = false;
ret = true;
printf("%d\n", sizeof(ret)); //vs2013 和 Linux中都是1
system("pause");
return 0;
}
为了深刻的理解布尔类型,我们要去查看布尔类型的源码
在vs中,光标选中bool,双击,可以转到定义,就能看到BOOL是什么
bool就是用宏定义表示_Bool , 0就用false表示;1就用true表示所以bool就是表示真假的
#define bool _Bool //c99中是一个关键字哦,后续可以使用bool
#define false 0 //假
#define true 1 //真
2.10.2 bool值和0比较
#include <stdio.h>
#include <stdbool.h>
int main()
{
int pass = 0; //0表示假,C90,我们习惯用int表示bool
//bool pass = false; //C99
if(pass == 0)
{ //理论上可行,但此时的pass是应该被当做bool看待的,==用来进行整数比较,不推荐
//TODO
}
if(pass == false)
{ //不推荐,尽管在C99中也可行
//TODO
}
if(pass)
{ //推荐
//TODO
}
//理论上可行,但此时的pass是应该被当做bool看待的,==用来进行整数比较,不推荐
//另外,非0为真,但是非0有多个,这里也不一定是完全正确的
if (pass != 1){
//TODO
}
if (pass != true){ //不推荐,尽管在C99中也可行
//TODO
}
if (!pass)
{ //推荐
//TODO
}
return 0;
}
2.10.3 bool类型总结
2.10.4 浮点型和指针类型与零值的比较
相关分析在我的另一篇博客【C和0的不解之缘】里有详细的解析,请大家移步观看。
http://t.csdn.cn/4R2Uhhttp://t.csdn.cn/4R2Uh
2.11 switch、case组合
2.11.1 基本语法
switch(整型变量/常量/整型表达式)
{
case var1:
break;
case var2:
break;
case var3:
break;
default:
break;
}
2.11.2 不要拿青龙偃月刀去削苹果
#include <stdio.h>
int main()
{
int day = 1;
switch (day){
case 1:
printf("星期一\n");
break;
case 2:
printf("星期二\n");
break;
case 3:
printf("星期三\n");
break;
case 4:
printf("星期四\n");
break;
case 5:
printf("星期五\n");
break;
case 6:
printf("星期六\n");
break;
case 7:
printf("星期日\n");
break;
default:
printf("bug!\n");
break;
}
return 0;
}
2.11.3 规则:
把 default 子句只用于检查真正的默认情况。
在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数。
建议 for 语句的循环控制变量的取值采用“半开半闭区间”写法
半开半闭区间写法和闭区间写法虽然功能是相同,但相比之下,半开半闭区间写法写法更加
直观。循环次数明显,便于进行个数计算
半开半闭区间写法 | 闭区间写法 |
for(n = 0; n < 10; n++) { …… } | for(n = 0; n <= 9; n++) { …… } |
不能在 for 循环体内修改循环变量,防止循环失控
for (n = 0; n < 10; n++)
{
…
n = 8;//不可,很可能违背了你的原意
…
}
2.11.4 case在switch的作用是什么?
case本质是进行判断功能
2.11.5 break在switch中的作用是什么?
break本质其实是进行分支功能
2.11.6 没有break会有什么问题?
#include <stdio.h>
int main()
{
int day = 1;
switch (day){
case 1:
printf("星期一\n");
case 2:
printf("星期二\n");
case 3:
printf("星期三\n");
case 4:
printf("星期四\n");
break;
case 5:
printf("星期五\n");
break;
case 6:
printf("星期六\n");
break;
case 7:
printf("星期日\n");
break;
default:
printf("bug!\n");
break;
}
return 0;
}
//如果多个不同case匹配,想执行同一个语句,推荐做法:
#include <stdio.h>
int main()
{
int day = 6;
switch (day){
case 1:
case 2:
case 3:
case 4:
case 5:
printf("周内\n");
break;
case 6:
case 7:
printf("周末\n");
break;
default:
printf("bug!\n");
break;
}
return 0;
}
结论:case之后,如果没有break,则会依次执行后续有效语句,直到碰到break
2.11.7 default语句相关问题
default可以出现在switch内的任何部分
尽管如此,我们依旧强烈推荐default应该放在case语句的最后
2.12 goto关键字
一般来说,编码的水平与 goto 语句使用的次数成反比。有的人主张慎用但不禁用 goto语句,但是在《C语言深度解剖》这本书中,作者却主张禁用
自从提倡结构化设计以来, goto 就成了有争议的语句。首先,由于 goto 语句可以灵活跳转,如果不加限制,它的确会破坏结构化设计风格;其次, goto 语句经常带来错误或隐患。它可能跳过了变量的初始化、重要的计算等语句。
struct student *p = NULL;
…
goto state;
p = (struct student *)malloc(…); //被 goto 跳过,没有初始化
{
⋯
state:
//使用 p 指向的内存里的值的代码
⋯
}
如果编译器不能发觉此类错误,每用一次 goto 语句都可能留下隐患
2.13 void关键字
2.13.1 色即是空
void 有什么好讲的呢?如果你认为没有,那就没有;但如果你认为有,那就真的有。有点像“色即是空,空即是色”。
2.13.2 void a
2.13.3 void 修饰函数返回值和参数
如果函数没有返回值,那么应声明为 void 类型
在 C 语言中,凡不加返回值类型限定的函数,就会被编译器作为返回整型值处理。但是许多程序员却误以为其为 void 类型。为了避免混乱,我们在编写 C 程序时,对于任何函数都必须一个不漏地指定其类型。如果函数没有返回值,一定要声明为 void 类型。这既是程序良好可读性的需要。也是编程规范性的要求。另外,加上 void 类型声明后,也可以发挥代码的“自注释”作用。所
谓的代码的“自注释”即代码能自己注释自己
如果函数无参数,那么应声明其参数为 void
在 C 语言中,可以给无参数的函数传送任意类型的参数,若函数不接受任何参数,一定要指明参数为 void
2.13.4 void指针
千万小心又小心使用 void 指针类型
如果函数的参数可以是任意类型指针,那么应声明其参数为 void *
典型的如内存操作函数 memcpy 和 memset 的函数原型分别为:
void * memcpy(void *dest, const void *src, size_t len);
void * memset ( void * buffer, int c, size_t num );
这两个函数就用到了void*类型,关于函数的具体用法感兴趣的话可以自己去cplusplus上面查查看
2.13.5 void不能代表一个真实的变量
void 不能代表一个真实的变量。
因为定义变量时必须分配内存空间,定义 void 类型变量,编译器到底分配多大的内存呢?
void简单不?现在你觉得它到底是色,还是空呢?
2.14 return 关键字
2.14.1 内存管理
首先在介绍这个关键字之前,我们先去认识一下“栈”的概念。
知道了上面概念之后我们再看一下return
2.14.2 return相关
但是我们栈帧空间有限,所以我们要主动的去释放掉空间,return就是用来终止一个函数(释放空间)并返回其后面跟着的值
2.14.3 拓展:
return 语句不可返回指向“栈内存”的“指针”,因为该内存在函数体结束时被自动销毁
函数的返回值,就是通过寄存器的方式返回给函数调用方!
2.15 const 关键字
2.15.1 const关键字也许应该被替换为readonly
//习惯格式
const int a = 10;
const修饰的变量不可直接被修改,正常情况是只读,可以通过指针的形式间接去修改。例如:
int *p = &a;
//之后再通过对指针p进行修改来改变a的值
那么就有人会有疑问,既然能够被改变,那么我们要const何用?难不成是看C语言太简单了,给我们增加点难度?请看下去。
2.15.2 const价值
所有的报错都是在我们编译代码时报错,而不是把程序编过来运行起来报错,所以我们就要想办法早点发现代码错误,以减少成本。const修饰的变量就是不想让别人或者自己忘了,对这个变量进行了修改,让编译器直接进行修改式检查。所以const不能称为真正的常量(见下面代码),程序只有在运行的时候才能知道它的大小。
#include<stdio.h>
int main()
{
const int n = 100;
int arr[n]; //vs中是不通过的
//因为数组空间开辟时,他的元素个数是一定的,中括号里的必须是常量
//这就间接的说明了const修饰的不能算是真正的常量,我们叫它常变量
return 0;
}
而相对的,真正不可被修改的是字符串常量,大家下去可以自己动手定义一个字符型指针,将字符串赋给这个指针,让后再对指针进行解引用外加赋值,这时候编译器就会无情的发出告警!
2.15.3 const修饰数组
定义或说明一个只读数组可采用如下格式:
int const a[5]={1, 2, 3, 4, 5};
const int a[5]={1, 2, 3, 4, 5};
2.15.4 const修饰指针
const int *p; const int *const p; | // p 可变, p 指向的对象不可变 //指针 p 和 p 指向的对象都不可变 |
2.15.5 记忆和理解的方法:
先忽略类型名(编译器解析的时候也是忽略类型名),我们看 const 离哪个近。 “近水楼台先得月”,离谁近就修饰谁。
const int *p; const int *const p; | //const 修饰*p,p 是指针, *p 是指针指向的对象,不可变 //前一个 const 修饰*p,后一个 const 修饰 p,指针 p 和 p 指向的对象都不可变 |
2.15.6 const修饰函数参数
const 修饰符也可以修饰函数的参数,当不希望这个参数值被函数体内意外改变时使用。例如:
void Fun(const int i);
告诉编译器 i 在函数体中的不能改变, 从而防止了使用者的一些无意的或错误的修改。
2.15.7 const修饰函数返回值
#include <stdio.h>
//告诉编译器,告诉函数调用者,不要试图通过指针修改返回值指向的内容
const int* test()
{
static int g_var = 100;
return &g_var;
}
int main()
{
int *p = test(); //有告警
//const int *p = test(); //需要用const int*类型接受
*p = 200; //这样,【在语法/语义上】,限制了,不能直接修改函数的返回值
printf("%d\n", *p);
return 0;
}
//一般内置类型返回,加const无意义
2.16 最易变的关键字----volatile
这个关键字是最不为人知,但确实最考察一个程序员C语言学习深度一个关键字。
int i=10;
int j = i; //(1)语句
int k = i; //(2)语句
这时候编译器对代码进行优化,因为在(1)、(2)两条语句中, i 没有被用作左值。这时候
编译器认为 i 的值没有发生改变,所以在(1)语句时从内存中取出 i 的值赋给 j 之后,这个值并没有被丢掉,而是在(2)语句时继续用这个值给 k 赋值。编译器不会生成出汇编代码重新从内存里取 i 的值,这样提高了效率。但要注意: (1)、(2)语句之间 i 没有被用作左值才行。
我们再看一个例子:
volatile int i=10;
int j = i; //(3)语句
int k = i; //(4)语句
volatile 关键字告诉编译器 i 是随时可能发生变化的,每次使用它的时候必须从内存中取出 i 的值,因而编译器生成的汇编代码会重新从 i 的地址处读取数据放在 k 中。
这样看来,如果 i 是一个寄存器变量或者表示一个端口数据或者是多个线程的共享数据,就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问
const要求你不要进行写入就可以。volatile意思是你读取的时候,每次都要从内存读。两者并不冲突。
虽然volatile就叫做易变关键字,但这里仅仅是描述它修饰的变量可能会变化,要编译器注意,并不是它要求对应变量必须变化!这点要特别注意。
2.17 最会带帽子的关键字----extern
我们在写代码的时候并不是只在一个文件里面写,只在一个文件里面写会提高我们维护成本,所以我们要尽量使用多文件,关于多文件,我们在static那里提到过,忘记的可以回头瞅两眼再回来。我们使用多文件时,要保证我的文件之间是可以交互的(可以互相传递信息),那么这个时候就需要extern来帮我们告诉编译器:这个变量,这个函数……来自其它文件。
A.c 文件中定义: B.c 文件中用 extern 修饰:
int i = 10; extern int i; //写成 i = 10;行吗?
void fun(void) extern void fun(void); //两个 void 可否省略?
{
//code
}
C.h 文件中定义: D.c 文件中用 extern 修饰:
int j = 1; extern double j; //这样行吗?为什么?
int k = 2; j = 3.0; //这样行吗?为什么
2.18 struct 关键字
2.18.1 概念:
struct是一个神奇的关键字,我叫它“打包员”,因为它将一些相关联的数据打包成一个整体,方便使用。它就是我们C语言后面说的结构体。
2.18.2 空结构体大小
结构体所占的内存大小是其成员所占内存之和,这里绝不是简单的各个结构体成员大小相加,而是涉及到了另外一个概念:结构体的内存对齐,现在主要讨论空结构体的问题,之后再专门对“内存对齐”进行讲解,这里不多赘述。
struct student
{
}stu;
这个空结构体的内存是多大呢,我们再不同的编译器下,编译结果是不同的:
(1)VS下,我们的代码是不能被编译通过的
(2)gcc它允许我们的代码被编过,编译结果为0
(3)VC++ 6.0下也让编过,编译结果为1byte
2.18.3 柔性数组
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员,但结构中的柔性数组成员前面必须至少一个其他成员。 柔性数组成员允许结构中包含一个大小可变的数组。sizeof 返回的这种结构大小不包括柔性数组的内存。包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
typedef struct st_type
{
int i;
int a[0];
}type_a;
有些编译器会报错无法编译可以改成:
typedef struct st_type
{
int i;
int a[];
}type_a;
这样我们就可以定义一个可变长的结构体,用 sizeof(type_a)得到的只有 4,就是sizeof(i)=sizeof(int)。那个 0 个元素的数组没有占用空间,而后我们可以进行变长操作了。通
过如下表达式给结构体分配内存:
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
这样我们为结构体指针 p 分配了一块内存。用 p->item[n]就能简单地访问可变长元素,但是这时候我们再用 sizeof(*p)测试结构体的大小,发现仍然为 4。这是为什么?其实柔性数组就相当于模子,在定义这个结构体的时候,模子的大小就已经确定不包含柔性数组的内存大小。它与结构体没有什么关系,只是一个编外人员,不占结构体的编制。
上面既然用 malloc 函数分配了内存,肯定就需要用 free 函数来释放内存:free(p);
2.19 union 关键字
2.19.1 union相关规则
union 关键字的用法与 struct 的用法非常类似,但在本质上确实截然相反的。
union 维护足够的空间来置放多个数据成员中的“一种”,而不是为每一个数据成员配置空间,在 union 中所有的数据成员共用一个空间,同一时间只能储存其中一个数据成员,所有的数据成员具有相同的起始地址。
union StMa{
char character;
int number;
char *str;
double exp;
};
一个 union 只配置一个足够大的空间以来容纳最大长度的数据成员,以上例而言,最大
长度是 double 型态,所以 StMa 的空间大小就是 double 数据类型的大小。
2.19.2 大小端模式对于union类型数据的影响
请看下面的代码片段:
union
{
int i;
char a[2];
}*p, u;
p = &u;
p->a[0] = 0x39;
p->a[1] = 0x38;
//p.i 的值应该为多少呢?
大小端在前面signed、unsigned关键字那里已经讲过了,这里就不再多说了。
union 型数据所占的空间等于其最大的成员所占的空间。对 union 型的成员的存取都是相对于该联合体基地址的偏移量为 0 处开始, 也就是联合体的访问不论对哪个变量的存取都是从 union 的首地址位置开始。如此一解释,相信大家心里都已经有了答案了吧?大家可以把自己的答案打在评论区
2.20 enum 关键字
2.20.1 枚举
enum就是C语言里面的枚举类型,枚举不是像很多人说的那样没什么用,相反,它是很重要的
//定义方式
enum enum_type_name
{
ENUM_CONST_1,
ENUM_CONST_2,
...
ENUM_CONST_n
} enum_variable_name;
枚举具有自描述性,不用做过多的解释
2.20.2 枚举与#define 宏的区别
1), #define 宏常量是在预编译阶段进行简单替换。枚举常量则是在编译的时候确定其值。
2),一般在编译器里,可以调试枚举常量,但是不能调试宏常量。
3),枚举可以一次定义大量相关的常量,而#define 宏一次只能定义一个。
枚举显然是可以用宏常量来代替的,但是如果常量多且相关性强,最好使用枚举,而且如果我们需要大量的相关性强的常量,用宏定义就要定义很多次,这会让我们的精力白白浪费在定义上。还有就是枚举有语法检查
2.21 伟大的缝纫师----typedef 关键字
2.21.1 历史的误会----也许应该是 typerename
在实际项目中,为了方便,可能很多数据类型(尤其是结构体之类的自定义数据类型),需要我们重新取一个适用实际情况的别名。这时候 typedef 就可以帮助我们。例如:
typedef struct student
{
//code
}Stu_st,*Stu_pst;
//涉及到的命名规则前面已经提过了
2.21.2 扩展:
用typedef定义一种新类型时,不能使用该新类型再拼上其他的关键字 / 不能再引入其他关键字来修饰类型或者变量,比如:
typedef int int32;
unsigned int 32 b;
//这种修饰方式很明显就是错的
//不符合语法规范
3. 关键字分类
目前,已经把 C89(C90) 的所有关键字全部介绍完了。下面就要对关键字进行一下分类,方便大家理解。
3.1 数据类型关键字(12个)
char :声明字符型变量或函数
short :声明短整型变量或函数
int : 声明整型变量或函数
long :声明长整型变量或函数
signed :声明有符号类型变量或函数
unsigned :声明无符号类型变量或函数
float :声明浮点型变量或函数
double :声明双精度变量或函数
struct :声明结构体变量或函数
union :声明共用体(联合)数据类型
enum :声明枚举类型
void :声明函数无返回值或无参数,声明无类型指针
3.2 控制语句关键字(12个)
3.2.1 循环控制(5个)
for :一种循环语句
do :循环语句的循环体
while :循环语句的循环条件
break :跳出当前循环
continue :结束当前循环,开始下一轮循环
3.2.2.条件语句(3个)
if : 条件语句
else :条件语句否定分支
goto :无条件跳转语句
3.2.3. 开关语句 (3个)
switch :用于开关语句
case :开关语句分支
default :开关语句中的“其他”分支
3.2.4. 返回语句(1个)
return :函数返回语句(可以带参数,也看不带参数)
3.3.存储类型关键字(5个)
auto :声明自动变量,一般不使用
extern :声明变量是在其他文件中声明
register :声明寄存器变量
static :声明静态变量
typedef :用以给数据类型取别名(但是该关键字被分到存储关键字分类中,虽然看起来没什么相关性)
存储关键字,不可以同时出现,也就是说,在一个变量定义的时候,只能有一个
3.4.其他关键字(3个)
const :声明只读变量
sizeof :计算数据类型长度
volatile :说明变量在程序执行中可被隐含地改变
结语:
好了,C语言关键字部分已经全部讲完了,如果其中有问题的地方请大家指出来;如果觉得有用的千万不要忘了点赞、关注、收藏哦!我们下期再见!