目录
前言:
1.关键字分类
2.最宽宏大量的关键字-auto
3. 最快的关键字 - register
4.最会帽子的关键字 - extern
5.最名不符实的关键字 - static
6.单目操作符--sizeof
7.signed、unsigned 关键字
8.if else 组合
9.switch case 组合
10.do、while、for 关键字
11.goto 关键字
12.void 关键字
13.return 关键字
14.const 关键字也许应该被替换为readonly
15. 最易变的关键字 - volatile
16. struct 关键字
17.union 关键字
18.enum 关键字
19.typedef关键字
20.所有关键字汇总
20.1数据类型关键字(12个)
20.2控制语句关键字(12个)
20.3存储类型关键字(5个)
20.4其他关键字(3个)
经过学习之后我相信会对大家在c语言有更深入了解,我们通过了解这些关键字,其实就是将生活带入了计算机,也是在生活中发现c语言,比如我们生活有很多判断(if),起床或者不起床,每天都是上课(while),上课学数学有小数(float),有整数(int),学英语(char)。所以我们发现语言是起源生活,c语言是为了人们向计算机表述,让它理解我们的语言。同时它也帮助我们解决我们生活中的一些问题。
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 | 子程序返回语句(可以带参数,也可不带参数)循环条件 |
auto:在缺省的情况下,编译器默认所有的局部变量都是auto的。
我们用代码的形式展示出 int与auto int:
#include
auto int a = 10;//wearing 这里是有警告的
int main()
{
int a = 10;
auto int b = 10;//自动变量(局部变量,自动变量,临时变量,都是一回事,统称为局部变量)
printf("%d %d", a,b);
return 0;
}
//输入结果都为10 并没有没有不一样
补充:
局部变量:包含在代码块中的变量叫做局部变量。局部变量具有临时性。进入代码块,自动形成局部变量,退出代码块自动释放。
全局变量:在所有函数外定义的变量,叫做全局变量。全局变量具有全局性。
结论:1.auto只能用于定义局部变量而不能用于定义全局变量 2.auto已经很古老,基本永不使用。
我们在了解关键字-register时,很有必要了解一下存储分级。其实,CPU主要是负责进行计算的硬件单元,但是为了方便运算,一般第一步需要先把数据从内存读取到CPU内,那么也就需要CPU具有一定的数据临时存储能力。
如今在我们生活中,电脑基本上已经做到每个家庭都有一台,理论上我们的计算机是可以不用硬盘来提高访问速度,那么平常人是不用起怎么昂贵的电脑,但是通过“缓存技术”越发成熟让我们的电脑变得便宜了。寄存器起到不小的作用。
寄存器的认识
现在我们只要需要知道CPU内集成了一组存储硬件即寄存器
寄存器存在的本质
在硬件层面上,提高计算机的运算效率。
register修饰变量
寄存器变量是指一个变量直接引用寄存器,也就是对变量名的操作的结果是直接对寄存器进行访问,放入CPU寄存区中,从而达到提高效率的目的。
我们用来代码的形式来展示register
#define _CRT_SECURE_NO_WARNINGS
#include
//register int b = 0;// 这里会有warning“有坏的存储类”,因为全局会导致CPU寄存器被长时间占用
int main()
{
register int a = 0;
//scanf("%d", &a);//在vs2013时这里会报错 因为取地址访问的是内存,register访问的是寄存器-这里有点脱裤子放p的意思了
printf("%d\n", a);
//register int c = 0;
//..........
//register int m = 0;
//当我一直定义下去时 会报错,因为寄存器数量有限,所以不要大量使用
//如果一个变量需要高频读取,我们就用register,因为我们直接通过CPU直接访问寄存器,就不需要缓存,那么读取就会更快。
register int b = 0;
b = 200; //寄存器变量是可以被写入,但是写入就需要写回内存,后续还要读取检测的话,register的意义在哪呢?所以不要写入
printf("%d", b);
return 0;
}
总结如何使用register:
1. 局部的
2. 不会被写入的
3. 高频被读取的
4. 如果要使用,请不要大量使用
在文件内使用extern:在变量定义之前要使用该变量,就需要在使用之前用extern声明变量。这里因为系统是自上往下运行的,所以我们在mian函数后定义了需要声明。
#include
int main()
{
extern int x;
extern int y;
printf("%d %d", x, y);
return 0;
}
int x = 1;
int y = 2;
多个文件之间的引用:如果一个工程由多个源文件组成,在一个源文件中想引用另一个源文件中已定义的外部变量,也需要在引用变量的源文件中用extern关键字声明变量
在test.c文件中代码展示
int fun()
{
printf("hello bite");
return 0;
}
在game.c文件中代码展示
#include
extern int fun();
int main()
{
fun();
return 0;
}
这里有两个问题:1.变量声明时可不可以不用extern?
2.函数声明时可不可以不用extern?
extern int g_val ; 变量声明必须带上extern,如果不带上那么这里就是初始化变量了。
void show; 函数声明时可以不用声明的,这里函数可以不用因为函数定义取决于它有没有函数体,这里分号结束了,没有函数体时就直接被解释成函数声明了;但是建议声明,为了更便于直观理解。
一个好的函数声明是为了让他人看了之后能够获取更多信息,对函数有更多理解。如果有参数,声明时我们也应该同样的写上参数。
//text.c 文件中
int fun(int x,int y)
{
return x+y;
}
//game.c 文件中
#include
extern int fun(int x,int y);
int main()
{
fun(1, 2);
return 0;
}
我们声明变量时,是没有开辟空间的,所有的变量声明是不可以设置初值的。
#include
int main()
{
extern int a = 10;//这里报错 不允许对外部变量的局部声明使用初始值设定项
printf("%d", a);
return 0;
}
int a = 1;
介绍static之前,我们知道两个问题,1.全局变量可以跨文件访问吗?2.函数是可以跨文件访问吗?答案是:都可以的,因为在一定规模的项目,一定是多文件的,多个文件之间,后续一定进行数据“交互”,如果不能跨文文件,“交互”成本就会过高。
下面就引申出,在具体的应用场景中,我们不想局部变量或函数跨文件访问,只在想在文件内部被访问我们应该怎么办,毫无疑问选择static。那我们通过代码来深入了解两个结论。
//test.c文件
static int x=10 ;
static void fun()
{
printf("hello bite!");
}
//main.c文件
#include
extern void fun();
extern int x;
int main()
{
printf("%d", x);//这里是会报错的
fun();
return 0;
}
//错误 2 error LNK2019: 无法解析的外部符号 _fun,该符号在函数 _main 中被引用
//错误 3 error LNK2001: 无法解析的外部符号 _x
#include
static int y=10;
int main()
{
printf("%d", y);
return 0;
}
//但是这里是不会报错的
由上可得结论:1.static修饰全局变量,该变量只在文件内被访问,不能被外部其他文件访问 2.static修饰函数,该变量只在文件内被访问,不能被外部其他文件访问
通过上诉结论中static修饰全局变量中那么问题又来了,它是改变的是全局变量的生命周期呢?还是改变的作用域呢?那么我们通过代码来演示来理解:
#include
//i局部变量,局部临时性
//函数调用开辟空间并初始化
//函数结束释放空间
int fun()
{
//int i = 0; //当int i = 0时,在main函数中打印出结果为 1 1 1 1 1 1 1 1 1 1
static int i = 0;//static 当int i = 0时,在main函数中打印出结果为 1 2 3 4 5 6 7 8 9 10
i++;
printf("%d ", i);
}
//通过在int i = 0与static int i = 0对比下,在修饰局部变量i中,i是具有具有临时性的,结束函数后会被释放空间,
//那么经过多次打印都为1,就证明了i出函数释放了空间。但是i被static修饰后,它是没有释放的,每次都随着循环增加i的值.
//临时变量->全局生命周期,那么他的生命周期是延长了。i在主函数中不能被printf调用,而作用域是没有任何改变的。
int main()
{
//printf("%d", i);//这是会报错的,所以它作用范围只在fun函数中
int i = 0;
for (i = 0; i < 10; i++)
{
fun();
}
return 0;
}
补充知识--只做浅入了解
局部变量具有临时性:局部变量是放在栈区的,栈需要进栈出栈(先进后出,后进先出)。 static局部变量:是放在全局数据区,在整个“进程”运行生命周期内,都是有效的。
我们先了解一些C常见内置类型和自定义类型
先见一批内置类型,后面慢慢补充 :
char
short
int
long
long long
float
double
自定义类型:
数组
结构体
位段
枚举
联合体
那么为什么我们需要这么多类型呢?
因为应用场景的不同,解决应用场景对应得计算方式不同,需要空间的大小也是不同的。本质:用最小的成本,解决各种多样化的场景问题
sizeof:确定一种类型对应在开辟空间的时候的大小
我们通过代码演示sizeof的用法,当然sizeof也可以对自定义类型使用,这里就不过多展示。
#include
int main()
{
printf("%d", sizeof(char));//1
printf("%d", sizeof(short));//2
printf("%d", sizeof(int));//4
printf("%d", sizeof(long));//4
printf("%d", sizeof(long long));//8
printf("%d", sizeof(float));//4
printf("%d", sizeof(double));//8
return 0;
}
平时我们运用sizeof,以sizeof()的形式使用,那sizeof是函数吗?
我们四个句代码向展示,以便便于大家理解。那么这四句那些对的?那些是错误的呢?
#include
int main()
{
int a=10;
printf("%d", sizeof(a)); //1
printf("%d", sizeof(int));//2
printf("%d", sizeof a); //3
printf("%d", sizeof int); //4
return 0;
}
这里1 2 3 是正确的,4是错误的。因为sizeof是关键字,它不能直接去求另一个关键字的大小。虽然1 2的使用很像函数。但是我们通3就可以否定,因为函数是不可以这样使用,但是sizeof可以。
这里提这两点,1.sizeof求数组的大小,是整个数组的大小。 2.sizeof求指针的大小,在32位中不管是什么类型它都是4字节
因为signed unsigned 主要运用修饰数据的符号位,而数据的存和取是与signed、unsigned 修饰数据的符号位是息息相关的,所以我们主要是讲数据的存储。深入的理解他们之间亲密的联系。
我们掌握signed、unsigned关键字之前,应该掌握原返补的概念。
我们相信很多人想问一句为什么要掌握原返补呢?在数据存储过程中,任何数据在计算中,都必须被转化成二进制,因为计算机只认识二进制。而原返补这一概念相当于是二进制在计算机存储过程中的规则,计算机中储存的整型必须是补码。又因为一个变量的创建是要在内存中开辟空间的,空间的大小是根据不同的类型而决定的,那么singed与unsigned类型开辟的变量,它的数据数据在所开辟内存中到底是如何存储的呢?接下来我们对它展开主要探讨。
有符号数:
int a = 20;
int b = -10;
我们知道,编译器为 a 分配四个字节的空间。那如何存储呢?
首先,对于有符号数,一定要能表示该数据是正数还是负数。所以我们一般用最高比特位来进行充当符号位。
原码、反码、补码
计算机中的有符号数有三种表示方法,即原码、反码和补码。
三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位三种表示方法各不相
同。
如果一个数据是负数,那么就要遵守下面规则进行转化:
原码:直接将二进制按照正负数的形式翻译成二进制就可以。
反码:将原码的符号位不变,其他位依次按位取反就可以得到了。
补码:反码+1就得到补码。
如果一个数据是正数,那么它的原反补都相同.
无符号数:不需要转化,也不需要符号位,原反补相同。
对于整形来说:数据存放内存中其实存放的是补码
因为前面写过一篇关于数据存储文章,里面也详细的写道了有关原返补,大小端的概念。这里就写下网址:http://t.csdn.cn/9X8u2。
补充一:
在我们了解了原返补的规则下,我想说一下在计算机的存储形式是二进制的补码,但是取出来的时候我们是取的原码,因为通过大数据统计了在历史长河中人是更习惯于十进制的。返回原码时具有两种方法如下:
方法一:-20
1111 1111 1111 1111 1111 1111 1110 1100 补码
1111 1111 1111 1111 1111 1111 1110 1001
1000 0000 0000 0000 0000 0000 0001 0100 原码
方法二:-20
1111 1111 1111 1111 1111 1111 1110 1100 补码
1000 0000 0000 0000 0000 0000 0001 0011 反码+1
1000 0000 0000 0000 0000 0000 0001 0100 原码
方法一更利于我们理解,但是方法二是更有价值的,因为计算机硬件完成,原《=》返《=》补 其运算过程是相同的。可以只使用一条硬件电路,完成转化。
补码 :
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理; 同时,加法和减法也可以统一处理(CPU只有加法器)。此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
补充二:
这里有两行代码,是否书写正确呢。
int main()
{
unsigned int a = 10;
unsigned int b =-10;
return 0;
}
他是没有任何警告和报错的,它是能被编译的,所以它是正确的。那么我来深入了解一下unsigned int b =-10的存入; 如图:
那么通上图我们观察到,类型开辟空间时,里面存储的数据是与开辟空间无关的,那么这里的变量类型是什么时候有效果? 我们通过代码来展示:
int main()
{
unsigned int a = 10;
unsigned int b =-10;
printf("%d ", a);
printf("%d", b);
printf("%u ", a);
printf("%u", b);
return 0;
}
输出结果分别为 10 -10
10 4294967286
通过比较,我们在打印的时候我们用了%d %u进行对比,发现取的时候我们是用“对应”变量的数据类型进行读取之后,结果不同了。所以变量类型在读取的时候有效果。读取是步骤如下:
所以得结论:
存:字面数据必须先转成补码,在放入空间当中。所以,所谓符号位,完全看数据本身是否携带+-号。和变量是否有符号无关!
取:取数据一定要先看变量本身类型,然后才决定要不要看最高符号位。如果不需要,直接二进制转成十进制。如果需要,则需要转成原码,然后才能识别。(当然,最高符号位在哪里,又要明确大小端
十进制二进制快速转化口诀
口诀:1后面跟n个0,就是2的n次方
大小端的深入了解
1.如何理解大小端?
在内存中每个字节都是有地址的,所以的地址都是不同的,那我们为了方便地址的设置,那么肯定有大小,所以地址就有高地址和低地址之分。我们图来观察一下:
列如内存中数据-10的16进制:0x f6 ff ff ff,数据也要按照字节为单位划分成若干块,数据照字节为单位,也有高权值和低权值之分。12的权值>78的权值。
那么问题来了数据在内存中如何存放的呢? 无论如何放,只要同等条件去取, 都可以。 因为无论数据存在高地址还是低地址,他只是放入空间中,对内存中数据只有存和取的操作,所以如何放是对数据没有任何影响的。而对数据存放的影响是厂家,是因为在计算机中内存硬件是由厂家决定的,不同的厂家定的标准有所不同,所以存储方案不同。所以有了大小端存储,而不同的储存是不影响用户的使用。
大小端的概率概念
大端:按照字节为单位,低权值位数据存储在高地址处,就叫做大端
小端:按照字节为单位,低权值位数据存储在低地址处,就叫做小端
大小端是如何影响数据存储的
大小端存储方案,本质数据和空间按照字节为单位的一种映射关系
如何理解这句话呢,通俗来说就如何存的就应该如何取,而存和取是由系统决定的,这一过程是透明的,对用户使用是不影响的。下面我们以一副图片来描述这个过程:
探究char类型的取值范围[-128,127]中的-128
首先什么是取值范围呢?
所谓的特定数据类型,能表示多少个数据,取决于多个比特位对应得排列组合的个数
列如:2个bite:00 01 10 11 ->2^2个
2个bite:000 001 010 011 100 110 111 ->2^3个
这里两个比特位,能表示的数据个数为0 1 2 3,在2个比特位中排列的组合个数为4个
在计算机中4个字节为4*8个比特位,就有2^32个排列组合,对于计算机来讲,他不会浪费任何一个排列组合因为计算机要用最小的成本解决最大的数据计算问题。
在char类型原码中,1 111 1111->-127 [0-127] 0 111 1111->127 [0-127] 表示是否正确呢?一种是负数时的范围,一种是正数时的范围,那么在这两个中有两处都为0,用二进制表示:1000 0000 和0000 0000,而且取值范围[-127,127]。但是计算机是需要用最小的成本解决问题的,那么0只能有一种标识方案 。那么很显然在1000 0000 和0000 0000中有一个表示不是0。通过计算机的储存的属性我们不难判断1000 0000应该表示的不是0,因为存储是连续的-127到-1之后应该是0到127。因为前面被沾满了而1000 0000就有两种可能表示为-128和128。又因为它最高为1(000 0000)所以它只能表示-128.所以最终char类型的取值范围为[-128,127]。
前面论述char类型的取值范围,那具体怎么理解这个-128呢?我们再继续深挖。
#include
int main()
{
char c= - 128;
printf("%d",c);
retuen 0;
}
//打印出值为-128 ,这代码是没有问题的 ,所以就证实了-128是在取值的合法范围内的。
//在上面也说过,数据存储与类型是没有关系的,类型只是用来确定开辟空间的大小,
//这里char类型开辟空间为8bite ,存的时候应该是先从原码转成补之后在放入空间
//那么-128的原码:1 1000 0000(9个bite位)->反码:1 0111 1111 ->补码:1 1000 0000
//但是在存储的时候空间只有8个bite位,所以会发生截断(截断:相当于日常操作ctrl +alt+a 然后选取我们需要截图的一部分,而这里“系统”选取8bite存入)。存入补码:1000 0000
//发生了截断之后,当我们按照存的方式取的话,那么很遗憾是会错误的,
//补码:1000 0000 ->-1操作,反码:0111 1111->取反操作,原码:符号位不变,0000 0000
//所以这个是不对的,无法转换回来。所以它是半计算半规定的一种方式 ,如果是1000 0000直接取就行了-128
关于signed、unsigned有三道题,我相信看了之后会对你理解signed、unsigned有很大的帮助,网址如下,以便参考:http://t.csdn.cn/ANavO
什么是语句 - 补充
C语言中由一个分号;隔开的就是一条语句。
比如:
printf("hehe"); 1+2;
什么是表达式 - 补充
C语言中,用各种操作符把变量连起来,形成有意义的式子,就叫做表达式。(我给的定义,主要是帮助大家理解:) )
操作符:+,-,*,/,%,>,<,=,==...
基本语法 - 补充
语法结构:
//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;
}
当if做注释(不推荐)
#define _CRT_SECURE_NO_WARNINGS
#include
int main()
{
if (0)
{
printf("hello biet");
}
if (1)
{
printf("hello biet2");
}
return 0;
}
//因为if语句中()里面的值容易被改变 容易出现问题
在上述代码我们注意到了if语句的0和1;那么在c语言中:0为假,非0为真
if语句的细则
1.先执行()中的表达式或者函数,得到真假结果
2.条件 判定功能
3.进行 分支功能
bool 变量与"零值"进行比较
深入理解C 中 bool
语言有没有bool类型?
c99之前,主要是c90是没有的,目前大部分书,都是认为没有的。因为书,一般都要落后于行业。
但是c99引入了_Bool类型(你没有看错,_Bool就是一个类型,不过在新增头文件stdbool.h中,被重新用宏写成了bool,为了保证C/C++兼容性)。
//测试代码1
#include
#include //没有这个头文件会报错,使用新特性一定要加上
#include
int main()
{
bool ret = false;
ret = true;
printf("%d\n", sizeof(ret)); //vs2013 和 Linux中都是1
system("pause");
return 0;
}
//查看源码
/* stdbool.h standard header */
//stdbool.h
#ifndef _STDBOOL
#define _STDBOOL
#define __bool_true_false_are_defined 1
#ifndef __cplusplus
#define bool _Bool //c99中是一个关键字哦,后续可以使用bool
#define false 0 //假
#define true 1 //真
#endif /* __cplusplus */
#endif /* _STDBOOL */
/*
* Copyright (c) 1992-2010 by P.J. Plauger. ALL RIGHTS RESERVED.
* Consult your license regarding permissions and restrictions.
V5.30:0009 */
PS:理论上,表示真假,需要一个bit就够了,不过这个问题,还是要取决于编译器的理解。vs2013中认为是1个字节。
那么问题来了,我们后面该怎么给别人介绍C中bool?以及后面代码该怎么写?
给别人介绍:就按照上面的来就行。
代码怎么写:因为目前编译器对C99特性支持的并不全面,我们后面依旧默认使用C90的认识去编码即可,使用int表示真
假。
具体要结合实际情况去定。
/在vs中,看看下面的代码
//测试代码2
#include
#include
int main()
{
//在vs中,光标选中BOOL,单击右键,可以看到转到定义,就能看到BOOL是什么
BOOL ret = FALSE;
ret = TRUE;
printf("%d\n", sizeof(ret)); //输出结果是4,因为在源代码中,是这么定义的:typedef int BOOL;
system("pause");
return 0;
}
//我们发现,竟然也能编过。为什么呢?
//这都是Microsoft自己搞的一套BOOL值。在vs中转到BOOL对应的头文件,翻到最上面,就能看到微软的版权 //信息。
//那我们采用哪一个呢?
//微软吗?这是强烈不推荐的,因为好的习惯是:一定要保证代码的跨平台性,微软定义的专属类型,
//其他平台不支持。
//以后在语言编程层面上,凡是直接使用和平台强相关的内容,我们都不推荐。(不是针对谁)
//跨平台性?
//我们可以看到上面测试代码1,和测试代码2 在vs2013下都能编过(微软系的),
//但是在Linux中(centos 7),测试代码1,是可以编过的(因为是标准),但是测试代码2就过不了。
[whb@VM-0-3-centos code]$ cat test.c //具有跨平台性
#include
#include
int main()
{
bool ret = false;
return 0;
}
[whb@VM-0-3-centos code]$ gcc test.c //直接通过
[whb@VM-0-3-centos code]$ vim test.c
[whb@VM-0-3-centos code]$ cat test.c
#include
#include
int main()
{
BOOL ret = FALSE;
return 0;
}
[whb@VM-0-3-centos code]$ gcc test.c //直接报错
test.c: In function ‘main’:
test.c:5:5: error: unknown type name ‘BOOL’
BOOL ret = FALSE;
^
test.c:5:16: error: ‘FALSE’ undeclared (first use in this function)
BOOL ret = FALSE;
^
test.c:5:16: note: each undeclared identifier is reported only once for each function it
appears in
//所以,后面万一要用bool,强烈推荐C99标准的,摒弃微软
总结:
1. 优先使用c90,就是我们之前以及后面一直用的方式
2. 万一非得使用bool,推荐c99标准,不推荐MS自定义。
那么将了怎么久的bool,在C语言中如何进行 bool 值与0比较呢?
#include
#include
#include
int main()
{
int pass = 0; //0表示假,C90,我们习惯用int表示bool
//bool pass = false; //C99
if (pass == 0){ //理论上可行,但此时的pass是应该被当做bool看待的,==用来进行整数比较,不推荐
//TODO
}
if (pass == false){ //不推荐,尽管在C99中也可行,但是在c90中是不可取的
//TODO
}
if (pass){ //推荐,因为这里pass本身就是逻辑值,用pass将会更加直观和更加便利(这里直接省略判断)
//TODO
}
//理论上可行,但此时的pass是应该被当做bool看待的,==用来进行整数比较,不推荐
//另外,非0为真,但是非0有多个,这里也不一定是完全正确的
if (pass != 1){
//TODO
}
if (pass != true){ //不推荐,尽管在C99中也可行
//TODO
}
if (!pass){ //推荐
//TODO
}
system("pause");
return 0;
}
结论:bool类型,直接判定,不用操作符进行和特定值比较。
那么结论所说的基本写法符合if本身语法的?为什么?
答案是合法的,因为上述基本写法是符合if语句细则的
float 变量与"零值"进行比较
这里关于float在内存的存储,篇幅过长我们写一了篇博客供与参考,网址如下:http://t.csdn.cn/ImbTV
浮点数在内存中存储,并不想我们想的,是完整存储的,在十进制转化成为二进制,是有可能有精度损失的。注意这里的损失,不是一味的减少了,还有可能增多。浮点数本身存储的时候,在计算不尽的时候,会“四舍五入”或者其他策略 。那我们通过以下图进行观察:
当打印x时,我们发现这里并不是3.600......而是3.6000000000000001.0....,所以很明显出现精度损失。
当我们用double变量进行打印的时候,我们发现本来x-0.9打印出来是0.1000.......而这里却是0.0999....780......,而且打印y时也不是0.100000.......。我们通过if判断x-0.9是否等于y时我们发现结果是不等的。
结论:因为精度损失问题,两个浮点数,绝对不能使用==进行相等比较.
那么我们如何解决上面的浮点数不能比较的问题呢,首先我们要明白浮点数的精度损失它是有范围的,既然我们不能直接浮点数比较,那么我们应该抓浮点数精度的范围进行比较,因为它损失的是很小的,而且是在一定范围内的,比如该浮点数是1,精度损失后0.99999999999871..那么精度损失的范围是在-(1-0.99999999999871..)和1-0.99999999999871..之间的,这个数我们也可以来定义,也可以用系统定义的 DBL_EPSILON。
//我们通过两个伪代码来展示如何使用
if((x-y) > -精度 && (x-y) < 精度){
//TODO
}
//伪代码-简洁版
if(fabs(x-y) < 精度){ //fabs是浮点数求绝对值
//TODO
}
//我们通过库函数fabs求出两个数相减的绝对值,如果相减的绝对值是在规定范围内,那么判断为对
精度:
自己设置?后面如果有需要,可以试试,通常是宏定义。
使用系统精度?暂时推荐
#include //使用下面两个精度,需要包含该头文件
DBL_EPSILON //double 最小精度
FLT_EPSILON //float 最小精度
//代码调整后
#include
#include //必须包含math.h,要不然无法使用fabs
#include //必须包含,要不然无法使用系统精度
#include
int main()
{
double x = 1.0;
double y = 0.1;
printf("%.50f\n", x - 0.9);
printf("%.50f\n", y);
if (fabs((x - 0.9) - y) < DBL_EPSILON){ //原始数据是浮点数,我们就用DBL_EPSILON
printf("you can see me!\n");
}
else{
printf("oops\n");
}
system("pause");
return 0;
}
上述代码是比较两个浮点是否相等,因为当两个数相减时是为了得到0,但是他们相减时有精度损失的,如果这个进度损失是在这个范围的,那么就证实了他们相等。所以这里是为了证实x-0.9与0.1在他们的精度范围内他们是否相等的。那么我们发现比较两个浮点数的大小也是需要分><=的情况。
比如:设置精度为1E-2,也就是0.01。对于两个浮点数a、b,如果fabs(a-b)<=1E-2,那么就是相等了;类似的判断大于的时候,就是if 比如:设置精度为1E2,也就是0.0 1。对于两个浮点数a、b,如果Fabs(a-b)<=1E-2,那么就是相等了;类似的判断大于的时候,就是if((a>b)&& (fabs(a-b)>1 E-2));判断小于的时候,就是if ((a1E-2));。 (a>b)&(晶圆厂(a-b)>1E-2);判断小于的时候,就是if((a1E-2);
float变量与0比较和上述讨论有什么关系呢?
当我们用float变量定义的0时,它是存在精度损失的,我个人理解是,它是精度的损失而不是我们定义时的错误,它是存储时的机制问题,当我们float定义的0其实是在一定损失范围内是等于0的。但是它不等于(0),所以就有了上述我们对浮点数正确的比较,float变量与0比较它就有了意义。
#include
#include
#include
#include
int main()
{
double x = 0.00000000000000000000001;
//if (fabs(x-0.0) < DBL_EPSILON){ //写法1
//if (fabs(x) < DBL_EPSILON){ //写法2
if(x > -DBL_EPSILON && x < DBL_EPSILON){ //书中写法
printf("you can see me!\n");
}
else{
printf("oops\n");
}
system("pause");
retur
那么在写法中x > -DBL_EPSILON && x < DBL_EPSILON我们可以在大于小于后加上等于吗?为何不是>= && <= 这种写法?
在网上很多资料上是可以这样写的,但是这里我们是不推荐的,因为加上等于之后他是出现了逻辑上的错误,因为XXX_EPSILON是最小误差,是:XXX_EPSILON+n不等于n的最小的正数。XXX_EPSILON+n不等于n的最小的正数: 有很多数字+n都可以不等于n,但是XXX_EPSILON是最小的,XXX_EPSILON依旧是引起不等的一员。换句话说:fabs(x) <= DBL_EPSILON(确认x是否是0的逻辑),如果=,就说明x本身,已经能够引起其他和他+-的数据本身的变化了,这个不符合0的概念。
指针变量与“零值”进行比较
指针变量与“零值”进行比较的if语句应该怎么写?
int * p =NULL //定义指针一定要同时初始化,不然就会变成野指针。
//这里的NULL其实就是强制转化的0,强制转化只是改变了类型,
//但是没有改变它本身的储存数据,也就是没有改变它的二进制。
//通过对NULL的转定义,观察它的源码--//#define NULL ((void *)0) 这里就不过多介绍
(A)if(p==0); if (p!=0);
(B)if(p); if (!p);
(C)if(NULL==p);if(NULL !=p);
那一组是正确的呢?
(A)写法:p是整型变量?容易引起误会,不好。尽管NULL的值和0一样,但是意义不同
(B)写法:p是bool型变量?容易引起误会,不好。
(C)写法:这是推荐的,因为NULL就给指针用的,NULL和p比较就立马意识到p就是一个指针
if 语句后面的分号问题
#include
int main()
{
int x = 0;
if (x);
printf("hehe!");
return 0;
}
//这里本意是不打印hehe,但是由于if语句后面加了分号,分号后面就是表示语句结束,
//if语句后面只对有一条语句起判断作用,所以我们常常会用括号把我们需要内容包含在if语句中。
//所以相当于if语句变成了空语句,if语句就没有意义了,就直接打印了。
else 到底与哪个if配对呢?
通过代码测试来分析
int main()
{
int x = 0;
int y = 1;
if (10 == x)
if (11 == y)
printf("hello bit\n");
else
printf("hello world!\n");
system("pause");
return 0;
}
推荐写法:
int main()
{
int x = 0;
int y = 1;
if (10 == x)
{
if (11 == y)
{
printf("hello bit\n");
}
}
else
{
printf("hello world!\n");
}
system("pause");
return 0;
}
//当然,我课堂上写{}的方案,我也推荐。
//具体使用哪种风格,我不推荐书中的观点,就确定好,后面每家公司都有自己的编码规范,具体可以结合公司情况来定
//现在只要写的没有问题,不要省略{}即可
总结:上面主要讲了三个变量与0比较,作用就是我们对if与else的特殊的比较理解清楚了之后,当我们再次面对if与else时,我们就会发现如果我们遇到其他关于if和else问题之后,我们就会很容易理解了。
本次学习switch case 组合的重点就是:switch 语句的基本理解,case 的作用,break 的作用和
switch case 推荐规则。
基本语法结构 - 补充内容
switch(整型变量/常量/整型表达式){
case var1:
break;
case var2:
break;
case var3:
break;
default:
break;
}
通过简单的语法分析我们可以知道,在switch语法结构中,case本身是用来进行判定的,break是用来进行分支功能的,而default是用来处理异常情况的。
已经有if else为何还要switch case?
switch语句也是一种分支语句,常常用于多分支的情况。这种多分支,一般指的是很多多分支,而且判定条件主要以整型为
主,如:
输入1,输出星期一
输入2,输出星期二
输入3,输出星期三
输入4,输出星期四
输入5,输出星期五
输入6,输出星期六
输入7,输出星期日
如果写成 if else 当然是可以的,不过比较麻烦
代码如下:
#include
#include
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;
}
system("pause");
return 0;
}
如果多个不同case匹配,想执行同一个语句,推荐做法:
#include
#include
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;
}
system("pause");
return 0;
}
结论:case之后,如果没有break,则会依次执行后续有效语句,直到碰到break
case后面的值有什么要求吗?
switch(m) && case n
//其中m 和 n必须是什么类型变量或者表达式?
//case 语句后面是否可以是const修饰的只读变量呢?不行
#include
#include
int main()
{
const int a = 10;
switch (a){
case a: //不行
printf("hello\n");
break;
default:
break;
}
system("pause");
return 0;
}
default语句相关问题 - 补充内容
#include
#include
//deom1
int main()
{
int day = 10; //改成10,到default
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;
}
system("pause");
return 0;
}
//deom2
int main()
{
int day = 10;
switch (day){
default: //调整default位置,进行对比测试
printf("bug!\n");
break; //现场演示,注释break,观察现象
case 1:
case 2:
case 3:
case 4:
case 5:
printf("周内\n");
break;
case 6:
case 7:
printf("周末\n");
break;
}
system("pause");
return 0;
}
结论:尽管如此,我们依旧强烈推荐default应该放在case语句的最后
return在Switch语句中使用--补充
如何解决这一问题呢
这一节主要是认识break 和 continue 的作用
三种循环各自语法 - 补充内容
//while
条件初始化
while(条件判定){
//业务更新
条件更新
}
//for
for(条件初始化; 条件判定; 条件更新){
//业务代码
}
//do while
条件初始化
do{
条件更新
}while(条件判定);
三种循环对应的死循环写法 - 补充内容
while(1){
}
for(;;){
}
do{
}while(1);
文件操作--补充内容
任何c程序,在默认编译好之后,运行时,都会打开三个输入输出流。
stdin:标准输入,FILE*stdin,键盘
stdout:标准输出,FILE*stdout,显示器
stderr:标准错误,FILE*stderr,显示器
int main()
{
int x;
scanf("%d", &x);
printf("%d", x);
return 0;
}
//我们通过上面文件操作的初步了解,为什么我们这里输入,输出不需要调用文件就可以使用键盘输入,显示器就能读取,
//因为系统已经默认帮我们打开,这里我们只做了解
break && cntinue 的作用
通过代码 对break进行分析:
int main()
{
while (1){
char c = getchar();
if ('#' == c){
break;
}
printf("echo: %c\n", c);
}
system("pause");
return 0;
}
补充:这里简单的介绍getchar:int getc( FILE *stream );首先getchar的返回类型是int 而不是char类型。getc: get a character from stdin 在标准输入上获取一个字符,而这里的stdin:FILE *stdin其实就是文件,通过这个文件链接键盘实现输入,我们在printf其实也是是需要用它的。
测试用continue终端输入abcd#1234:
int main()
{
while (1){
char c = getchar();
if ('#' == c){
continue;
}
printf("echo: %c\n", c);
}
system("pause");
return 0;
}
总结:
break语句可以用来从循环体内中途跳出循环体,即提前结束循环操作,接着执行循环下面的语句。
continue语句是跳过循环体中剩余的语句而强制执行下一次循环操作。其作用为结束本次循环,即跳过循环体中下面尚未执行的语句,接着进行下一次是否执行循环的判定。
在for语句continue是在哪儿结束呢?是在如下代码中的1,2,还是3呢?
很显然是3,如果是1的话又开始直接又从i=0开始了,如果是2的话那么就一直判断了,所以1和2就会发生死循环,3的话就先++为4再判断的
讨论goto关键,其实很多时候是说它不建议被使用,但这至少对于我们初学者来说,goto用的是比较活的,当用的不好的时候容易出现差错。但是呢,在很多大项目中是大面积使用到了共同关键字的。Linux内核源代码中充满了大量的goto,只能说我们目前,或者很多公司的业务逻辑不是那么复杂 。
#include
#include
int main()
{
int i = 0;
START:
printf("[%d]goto running ... \n", i);
Sleep(1000);
++i;
if (i < 10){
goto START;
}
printf("goto end ... \n");
system("pause");
return 0;
}
//这里申明一下goto关键字只能用于代码块内
void是否可以定义变量
#include
#include
int main()
{
void a;
system("pause");
return 0;
}
在vs2013和Centos 7,gcc 4.8.5下都不能编译通过
为何 void 不能定义变量
定义变量的本质:开辟空间
而void作为空类型,理论上是不应该开辟空间的,即使开了空间,也仅仅作为一个占位符看待
所以,既然无法开辟空间,那么也就无法作为正常变量使用,既然无法使用,编译器干脆不让他定义变量。
在vs2013中,sizeof(void)=0
在Linux中,sizeof(void)=1(但编译器依旧理解成,无法定义变量)
void修饰函数返回值和参数
#include
#include
void show()
{
printf("no return value!\n");
}
int main()
{
show();
system("pause");
return 0;
}
如果自定义函数,或者库函数不需要返回值,那么就可以写成void
那么问题来了,可以不写吗?不可以,自定义函数的默认返回值是int(这个现场验证)
所以,没有返回值,如果不写void,会让阅读你代码的人产生误解:他是忘了写,还是想默认int?
结论:void作为函数返回值,代表不需要,这里是一个"占位符"的概念,是告知编译器和给阅读源代码的工程师看的。
//场景2
//void 作为函数参数
//如果一个函数没有参数,我们可以不写, 如test1()
#include
#include
int test1() //函数默认不需要参数
{
return 1;
}
int test2(void) //明确函数不需要参数
{
return 1;
}
int main()
{
printf("%d\n", test1(10)); //依旧传入参数,编译器不会告警或者报错
printf("%d\n", test2(10)); //依旧传入参数,编译器会告警(vs)或者报错(gcc)
system("pause");
return 0;
}
结论:如果一个函数没有参数,将参数列表设置成void,是一个不错的习惯,因为可以将错误明确提前发现
另外,阅读你代码的人,也一眼看出,不需要参数。相当于"自解释"。
void 指针
void不能定义变量,那么void*呢?
#include
#include
int main()
{
void *p = NULL; //可以
system("pause");
return 0;
}
为什么void*可以呢?因为void*是指针,是指针,空间大小就能明确出来
void* 能够接受任意指针类型
#include
#include
int main()
{
void *p = NULL;
int *x = NULL;
double *y = NULL;
p = x; //虽然类型不同,但是编译器并不报错
p = y; //同上
system("pause");
return 0;
}
//反过来,在vs/gcc中也没有报错。书中编译器很老了,我们严重不推荐
结论:但是我们依旧认为,void*的作用是用来接受任意指针类型的。这块在后面如果想设计出通用接口,很有用
比如: void * memset ( void * ptr, int value, size_t num );
void * 定义的指针变量可以进行运算操作吗
在vs2013中 :
#include
#include
int main()
{
void *p = NULL;
p++; //报错
p += 1; //报错
system("pause");
return 0;
}
在gcc4.8.5中:
#include
int main()
{
void *p = NULL; //NULL在数值层面,就是0
p++; //能通过
printf("%d\n", p); //输出1
p += 1; //能通过
printf("%d\n", p); //输出2
return 0;
}
什么在不同的平台下,编译器会表现出不同的现象呢?
根本原因是因为使用的C标准扩展的问题。具体阅读书。
我这里有问题:
对指针++具体是怎么++?
为何gcc中,输出结果是 1 和 2 呢?
本质和不同平台,看待void空间大小相关。具体现场演示。
补充内容:
GNU计划,又称革奴计划,是由Richard Stallman(理查德·斯托曼)在1983年9月27日公开发起的。它的目标是创建一套完
全自由的操作系统。它在编写linux的时候自己制作了一个标准成为 GNU C标准。ANSI 美国国家标准协会,它对C做的标准
ANSI C标准后来被国际标准协会接收成为 标准C 所以 ANSI C 和标准C是一个概念,总体来说现在linux也支持标准C,以
后标准C可以跨平台,而GUN c 一般只在linux c下应用。--来自百度
Linux 上可用的 C 编译器是 GNU C 编译器,它建立在自由软件基金会的编程许可证的基础上,因此可以自由发布。 GNU
C 对标准 C 进行一系列扩展,以增强标准 C 的功能。--来自百度
一句话,大部分编译器是标准C,而Linux下是扩展C,Linux平台也能保证标准C的运行。
void*指针可以直接解引用吗?
int main()
{
void* p = NULL;
*p = (int*)20;
return 0;
}
是不可以的,因为虽然void*指针是有地址的,但是对它进行解引用就成为变量,void的变量是在vs2013中无法开辟空间的,所以是无法直接解引用的。
在探讨 return 关键字时我建议,可以先看我写的函数栈帧,里面说了一些基础的汇编指令,函数的创建和函数的销毁,以便于更好的理解return关键字。网址如下:http://t.csdn.cn/iXjrw
这里有个问题,在计算机中,释放空间是否真的要将我们的数据全部清0/1?
有这样一个比喻:当我们建房时和拆房时他们的速度是不一样的,建房需要慢慢的一层一层的修,但是在拆房时,我们经常看见有个拆字,后续这块地是重建还是重装就可以后面的了。
那么回答这个问题:计算机中清空数据,只要设置该数据无效即可 。
理解下面代码:
#include
#include
char* show()
{
char str[] = "hello bit";
return str;
}
int main()
{
char *s = show();
printf("%s\n", s);
system("pause");
return 0;
}
因为show函数是在栈上开辟的空间,栈帧是有创建和销毁的,当我们运行完时这段空间将会被默认为无效,而我们返回的地址(在寄存器eax被临时保存)被接受之后,printf库函数通过接受的地址去打印,但是已经被覆盖,所以是打印出来是随机值。
我们知道了地址是不能返回过来的,那如果返回的是变量呢?
#include
#include
int test()
{
int a = 10;
return a;
}
int main()
{
int a = test();
printf("%d\n", a);
//test(); //没有变量接收返回值的时候,有返回值吗?
system("pause");
return 0;
}
我们发现在test函数中return a;是可以被被接受的,既然a是函数定义的变量,具有临时性,那么这个临时变量在函数退出的时候,应该被释放。这个是为什么呢?我们通过反汇编来了解。
最开始我们发现test中a是10,那么经过出栈之后内存空间被默认为是无效数据,之后被覆盖。返回值a就变成了随机乱码。
当我们进入主函数后,我们发现神奇a又变回10了,这个是为什么呢?
我们进入反汇编,我们发现a的值是先move(存放)寄存器eax(临时存放数据)中,然后再从eax中取出来的值move到a中。
const 修饰的只读变量
//const修饰变量
#include
#include
int main()
{
const int i = 10;
i = 20; //报错,代表变量i不可以直接被修改
system("pause");
return 0;
}
const修饰变量真的不能被修改吗?
#include
#include
int main()
{
const int i = 10;
int *p = (int*)&i;
printf("before: %d\n", i);
*p = 20;
printf("after: %d\n", i);
system("pause");
return 0;
}
结论:const修饰的变量并非是真的不可被修改的常量
那const修饰变量,意义何在?
1. 让编译器进行直接修改式检查
2. 告诉其他程序员(正在改你代码或者阅读你代码的)这个变量后面不要改哦。也属于一种“自描述”含义
const修饰的变量,可以作为数组定义的一部分吗?
#include
#include
int main()
{
const int n = 100;
int arr[n];
system("pause");
return 0;
}
在vs2013(标准C)下直接报错了,但是在gcc(GNU扩展)下,可以
但我们一切向标准看齐,不可以。
const修饰数组
int main()
{
const int arr[5] = { 1, 2, 3, 4, 0 };
arr[0] = 1;
arr[1] = 1;
arr[2] = 1;
return 0;
}
当const修饰数组,那么就变成了只读数组
const修饰指针
指针和指针变量
指针:C语言中,指针就是变量的地址。
指针变量:一个变量的值是另一个变量的地址,且变量类型相同,则称该变量为指针变量。
注:
- 指针变量定义时,也会被分配内存空间;
*
号表示所定义的变量为指针变量;- 改变指针变量的值实际上是改变指向;
- 指针变量的类型必须与指向的变量类型保持一致。
&地址
这里有必要跟大小端区分开,大小端是数据在内存中存储,这里是变量的地址。每个类型都是4字节,通过观察不同类型发现,我们取地址是从最小的开始。
总结:在c语言中,任何变量&都是最低地址开始的。
解引用
(类型相同)对指针进行解引用,指针所指向的目标
通过四种情况来讨论const
int main()
{
int a = 10;
const int*p=&a;
*p = 100;
p = 100;
return 0;
}
int main()
{
int a = 10;
int const *p = &a;
*p = 100;
p = 100;
return 0;
}
总结:p指向的变量不可以直接被修改
int main()
{
int a = 10;
int* const p = &a;
*p = 100;
p = 100;
return 0;
}
总结:p的内容不可直接被修改,p的指向不能改
int main()
{
int a = 10;
const int* const p = &a;
*p = 100;
p = 100;
return 0;
}
总结:p的指向不能改,p指向的变量也不可以直接被修改
sonst的小经验
如果我们把一个类型限定的不怎么严格的变量赋给另一限定严格的变量编译器是不会有警告的,但如果把一个类型限定严格的变量赋给不怎么严格的变量编译器会报出警告。
例子如下:
int main()
{
int a = 10;
const int *p = &a;
int *q = p;
return 0;
}
int main()
{
int a = 10;
const int *p = &a;
int *q = p;
return 0;
}
const修饰函数的参数
下面show函数会形成临时变量?
void show(const int*_p)
{
printf("show %p\n", &_p);
}
int main(){
int a = 10;
int *p = &a;
printf("mian %p\n", &p);
show(p);
return 0;
}
总结:任何函数参数都一定会形成临时,包括指针
修饰函数返回值
#include
#include
//告诉编译器,告诉函数调用者,不要试图通过指针修改返回值指向的内容
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);
system("pause");
return 0;
}
总结:一般内置类型返回,加const无意义。
接下来,我在汇编角度,在Linux平台给大家对比演示一下加还是不加volatile的作用,让大家看明白。
不加volatile
[whb@VM-0-3-centos code]$ cat test.c
#include int pass = 1;
int main()
{
while(pass){ //思考一下,这个代码有哪些地方,编译器是可以优化的。
}
return 0;
}
whb@VM-0-3-centos code]$ gcc test.c -O2 -g //以O2级别进行代码优化 [whb@VM-0-3-centos code]$ objdump -S -d a.out > a.s //对形成的a.out可执行程序进行优化 [whb@VM-0-3-centos code]$ vim a.s //查看汇编代码
加volatile
whb@VM-0-3-centos code]$ cat test.c
#include
volatile int pass = 1; //加上volatile
int main(){
while(pass){
}
return 0;
}
[whb@VM-0-3-centos code]$ gcc test.c -O2 -g //以O2级别进行代码优化
[whb@VM-0-3-centos code]$ objdump -S -d a.out > aa.s //对形成的a.out可执行程序进行优化
[whb@VM-0-3-centos code]$ vim aa.s //查看汇编代码
结论:volatile忽略编译器的优化,保持内存可见性。
其他问题
const volatile int a = 10;在vs2013和gcc 4.8中都能编译通过
const是在编译期间起效果//volatile在编译期间主要影响编译器,形成不优化的代码,进而影响运行,故:编译和运行都起效果。
const要求你不要进行写入就可以。volatile意思是你读取的时候,每次都要从内存读。两者并不冲突。
虽然volatile就叫做易变关键字,但这里仅仅是描述它修饰的变量可能会变化,要编译器注意,并不是它要求对应变量必须变化!这点要特别注意。
为什么c语言中必须存在结构体关键字呢?
因为我们计算的场景他需要我们定义结构体,比如生活中的一件事物,它是需要各种数据来描述的。(比较抽象)
结构体的本质:具有不同类型的集合
struct的基本使用
#define _CRT_SECURE_NO_WARNINGS
#include
struct student
{
int age;
char name[10];
char sex;
};
int main()
{
struct student arr[30] ;
struct student x;
strcpy(x.age, 22);
struct student *p = &x;
printf("%d\n", p->age);
printf("%s", p->name);
return 0;
}
结构体访问为什么有两种访问方式呢?
结构体在它定义的时候访问用 .(点)更方便一些,如果传参的话,用指向操作符更方便些。
空结构体有多大?
结论: 我们子vs2013中不能定义空的结构,但是在gcc中是可以的,我们发现是大小为0。
柔性数组
1.柔性数组在结构体的大小是未知的,在sizeof中不计算其大小
#include
struct S
{
int n ;
int arr[0];//或者int arr[];
};
main()
{
printf("The size of the structure is %d",sizeof(struct S));
return 0;
}
2.在结构体中,如果存在柔性数组,就必须满足它的前面含有其它的成员,来申请空间,,因为他的大小本身是未知的,不计算大小。
3.包含柔性数组的结构体要靠malloc去动态申请这块空间,这就体现了柔性数组的柔,也就是可以控制大小,且分配的空间一定满足大于其它成员的大小。
指针动态开辟
#include
#include
struct S
{
int n;
int* arr;
};
int main()
{
struct S* ps = (struct S*)malloc(sizeof(struct S));
ps->n = 100;
ps->arr = (int*)malloc(40);
free(ps->arr);
ps->arr = NULL;
free(ps);
ps = NULL;
return 0;
}
union stu
{
int a;
char b;
};
int main()
{
union stu x;
printf("%p\n",&x);
printf("%p\n",&(x.a));
printf("%p\n", &(x.b));
return 0;
}
总结:联合体内,所有成员的起始地址都是一样的
union的空间分布
总结:内部最大的进行申请空间,所有其他的都从这份空间最低地址处开始放射状向高地址出申请空间,占多拿多少
大小端对于union的影响
union stu
{
int a;
char b;
};
int main()
{
union stu x;
x.a = 1;
if (x.b == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
总结:在vs2013中联合体中数据是小端存储
下面代码输出结果为?
union un{
int i;
char a[4];
}*p,u;
int main()
{
p = &u;
p->a[0]= 0x39;
p->a[1]= 0x38;
p->a[2]= 0x37;
p->a[3]= 0x36;
printf("%x", p->i);
return 0;
}
首先我们知道小端存储,低低低,那么打印出来36373839;
枚举类型的使用方法
#include
enum DAY
{
MON = 1, TUE, WED, THU, FRI, SAT, SUN
} day;
int main()
{
// 遍历枚举元素
for (day = MON; day <= SUN; day++) {
printf("枚举元素:%d \n", day);
}
}
1.枚举类相当于是制作的一组强相关的常量。
2.枚举它具有自描述性
enum 与 #define 的区别
如果代码量比较多,建议使用enum
1.枚举更加简洁
2.枚举更有相关性,宏看不出来相关性
3.宏在预处理的时候就会被替换掉,而枚举会做语法检查。
简化认识typedef
其实就是一个含义:类型重命名
1. 对一般类型进行重命名
2. 对结构体类型进行重命名
3. 对指针进行重命名
4. 对复杂结构进行重命名
typedef 与 #define 的区别
/问题1:
typedef int * ptr_t;
ptr_t p1,p2;
问:p1,p2分别是什么类型
#define PTR_T int*
PTR_T p1, p2;
问:p1,p2分别是什么类型
//问题2:下面关于typedef部分的代码对吗?
#include
#include
#define INT32 int
typedef int int32;
int main()
{
unsigned int32 a = 10; //C中typedef不支持这种类型的扩展,不能当成简单的宏替换
//unsigned INT32 a = 20; //宏简单替换,可以
//printf("%d\n", sizeof(struct stu));
system("pause");
return 0;
}
//问题3
typedef static int int32_t 行不行
char :声明字符型变量或函数
short :声明短整型变量或函数
int : 声明整型变量或函数
long :声明长整型变量或函数
signed :声明有符号类型变量或函数
unsigned :声明无符号类型变量或函数
float :声明浮点型变量或函数
double :声明双精度变量或函数
struct :声明结构体变量或函数
union :声明共用体(联合)数据类型
enum :声明枚举类型
void :声明函数无返回值或无参数,声明无类型指针
1. 循环控制(5个)
for :一种循环语句
do :循环语句的循环体
while :循环语句的循环条件
break :跳出当前循环
continue :结束当前循环,开始下一轮循环
2. 条件语句(3个)
if : 条件语句
else :条件语句否定分支
goto :无条件跳转语句
3. 开关语句 (3个)
switch :用于开关语句
case :开关语句分支
default :开关语句中的“其他”分支
4. 返回语句(1个)
return :函数返回语句(可以带参数,也看不带参数)
auto :声明自动变量,一般不使用
extern :声明变量是在其他文件中声明
register :声明寄存器变量
static :声明静态变量
typedef :用以给数据类型取别名(但是该关键字被分到存储关键字分类中,虽然看起来没什么相关性)
const :声明只读变量
sizeof :计算数据类型长度
volatile :说明变量在程序执行中可被隐含地改变
很高兴大家能够阅读,也希望大家收藏,把它当做关键字的字典也是不错一个选择 。