系列文章目录
第一回 三十二个关键字 心性修持大道生
第二回 悟彻指针真妙理 归本数组合元神
第二回在这里!!!
文章目录
- 系列文章目录
- 一、默默无闻---auto
- 二、心急如焚---register
- 三、生人勿近---static
- 四、梅山六兄弟
- 五、百口莫辩---sizeof
- 六、正负的宿命---signed、unsigned
- 七、天下英雄,唯if与else耳。
- 八、只要能够到达那个地方---switch、case
- 九、周而复始---do、while、for
- 十、任务已接受,我们要去哪---goto
- 十一、你猜我存在不存在---void
- 十二、归来吧---return
- 十三、我的心永远属于你---const
- 十四、水很深,我能把握住---volatile
- 十五、我来自远方---extern
- 十六、有容乃大---struct
- 十七、我们联合---union
- 十八、枚举法天下无敌---enum
- 十九、懒人必备---typedef
提示:以下是本篇文章正文内容。
一、默默无闻—auto
在C98中,我们极少使用auto,因为编译器默认所有变量都是auto的。因此得名“默默无闻”。
在C11中,auto摇身一变,可以在声明变量的时候根据变量初始值的类型自动为此变量选择匹配的类型。
auto a = 5.2;
printf("%f", a);
如以上变量a被判断为double类型,需要用%f的格式来输出。
二、心急如焚—register
register请求编译器尽可能让变量a直接放在寄存器里面,因为将变量存储在CPU内部寄存器里时访问速度更快。
为什么是尽可能呢?
想一想,CPU寄存器的数量有限,相比于内存太小了。
如果定义很多register变量,想放也放不下了。
1.什么是寄存器?
寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。
2.使用register的注意点:
-
register变量必须是能被CPU寄存器接受的类型。这意味着register必须是一个单个的值,其长度应小于等于整形的长度。
-
register变量不一定存放在内存里,所以不能用&获取其地址。
三、生人勿近—static
1.修饰变量
static是静态的意思,在全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量。
#include<stdio.h>
static int b;
int main()
{
printf("%d", b);
}
以上代码中全局变量b的作用域只在本文件中,其他文件即使使用了extern也没法使用。我称之为“生人勿近”。
如果没有初始化静态全局变量,它的值默认为0.
如果我们在函数体里面定义静态局部变量,就只能在这个函数里使用了,同一个文件中的其他函数也不能使用。由于被static修饰的变量总是在内存的静态区,所以即使这个函数运行结束,这个静态变量的值也不会被销毁,函数下次使用时仍然能用到这个值。
我们看以下代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
static int j;
void fun1()
{
static int i = 0;
i++;
printf("I:%d\n", i);
}
void fun2()
{
j = 0;
j++;
printf("J:%d\n", j);
}
int main()
{
int k = 0;
for (k = 0; k < 10; k++)
{
fun1();
fun2();
}
return 0;
}
结果是fun1()中的i每次循环+1,最终到10;
而fun2()中的j每次更新为0并且++,最终为1.
2.修饰函数
函数前加上static使得函数变为静态函数,作用域仅局限于本文件。
四、梅山六兄弟
C语言中有一些基本数据类型,整型、字符型、浮点型…
在VS2019下,
牛魔王—char,占1个字节
蛟魔王—short,占2个字节
鹏魔王—int,占4个字节
狮驼王—long,占4个字节
猕猴王—float,占4个字节
禺狨王—double,占8个字节
五、百口莫辩—sizeof
sizeof作为一个关键字,用于计算各种类型、变量的大小,常年被误解为函数。原因是,使用时,身后的玩意会加上括号。
我们作以下测试:
int main()
{
int i = 0;
printf("%d\n", sizeof i);
}
竟然是可以运行的,而且结果正确。
括号呢???没有括号竟然也行。
但是函数可不能没有括号,这也验证了sizeof不是函数。
(当然sizeof int 是不行的).
总结:sizeof在计算变量大小所占空间时,括号可以省略,
但是计算类型大小时不能省略。
六、正负的宿命—signed、unsigned
配图(记忆不只是0到1)
计算机只认识0和1,数据到了底层都转化为0,1.
那么问题来了,我们怎么表示负数呢?
我们约定最高位如果是1,就为负数;如果为0,就是正数。
signed为有符号,符合以上约定。
unsigned为无符号数,均为非负数。
来看这题:
#include<stdio.h>
#include<string.h>
int main()
{
char a[1000];
int i;
for (int i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d", strlen(a));
return 0;
}
没想到吧,结果是255.我是题解,不懂点我
七、天下英雄,唯if与else耳。
#include<stdio.h>
#include<string.h>
int main()
{
int input = 0;
scanf("%d", &input);
if (input)
{
printf("窝窝头\n");
}
else
{
printf("嘿嘿\n");
}
}
如果if后的条件为真,则执行if后的语句,
否则执行else后的语句。
八、只要能够到达那个地方—switch、case
if、else一般表示两个分支或者嵌套比较少的分支,如果分支很多,还是用switch、case组合吧。
switch 是多分支选择语句,也就是多个 if。
基本格式为:
switch (variable)
{
case Value1:
//program code
break;
case Value2:
//program code
break;
case Value3:
//program code
break;
default:
break;
}
- 每个case语句的结尾绝对不要忘了加break,否则会按顺序把每个case都执行一遍。
- 最后必须用default分支,这是输入的变量值没有与之匹配的case的默认选项。
九、周而复始—do、while、for
C语言中循环语句有3种:while循环、do-while循环、for循环。
while循环:先判断括号后面里的值,如果为真则进入循环。
while(judge)
{
judge--;
}
do-while循环:先执行一次循环体里的代码,然后判断判断括号后面里的值,如果为真则继续进入循环。
do
{
t+=5;
}while(m);
for循环:求解表达式1,求解表达式2,若其值为真,则执行 for 语句中指定的内嵌语句,然后执行表达式3;回到表达式2,若表达式2值为假,则结束循环,求解表达式3。
for(i=0;i<5;i++)
{
printf("%d",i);
}
十、任务已接受,我们要去哪—goto
goto语句可以灵活跳转,但似乎会出很多大问题,建议别用。
十一、你猜我存在不存在—void
1.void a
void是空类型,一般没有人会定义一个空类型变量。
2.void作为函数返回值和参数
有些函数没有返回值,我们可以定义为void;
有些函数没有参数,我们也可以定义为void。
如以下代码:
static int a = 5;
void Print(void)
{
printf("%d", a);
}
Print无需返回,也无参。
(配图我真的什么都没有了)
3.void 指针
定义一个指针后,如果暂时不用就要给他赋值NULL,这样可以避免很多意料之外的错误。
十二、归来吧—return
return用来终止一个函数并返回其后面跟着的值。
函数定义为什么样的返回类型,该函数中return后就应该是相应类型的值。
如果实在不需要函数返回什么值,就需要用void声明其类型。
int main()
{
//program code
return 0;
}
十三、我的心永远属于你—const
const是恒定不变的意思,一旦被const修饰,魂就被勾走了,不会改变了。
1.const修饰的只读变量
定义const只读变量,具有不可变性。
int main()
{
const int size = 100;
int array[size] = { 0 };
}
以上代码在C++中可以正常运行,而在C语言中会报错。
原因是const修饰的常量实际上是常变量。
2.const修饰一般变量
一般变量是指类型简单的制度变了。这种变量在定义时,修饰符const可以用在类型说明符前,也可以在类型说明符后。
int const i = 2;
const int i = 2;
以上的定义都是正确的。
3.const修饰数组
int const a[5] = {0};
const int a[5] = {0};
以上格式也都是正确的。
4.修饰指针
const int* p;
int const* p;
int* const p;
const int* const p;
谁是不变的呢?
我们用故事理解:
5.修饰函数的参数
const修饰符可以修饰函数的参数,当不希望这个参数值在函数体内被意外改变时可以使用,这样可以增强代码的健壮性。
如:
void Function(const int *p);
这等于告诉编译器,*p是我的人,你小子别乱动。
十四、水很深,我能把握住—volatile
volatile是易变的、不稳定的意思。
他和const一样是一种类型修饰符,用它修饰的变量,编译器可以提供对特殊地址的稳定访问。
来看下面的例子:
int i = 0;
int j = i;//语句1
int k = i;//语句2
在语句1,2中,i 没有被赋值,编译器认为 i 是不变的。
因此给 j 赋值后再给 k 赋值时,编译器并没有去 i 的地址找 i ,而是沿用刚刚取出的 i 。
再看另一个例子:
volatile int i = 0;
int j = i;
int k = i;
volatile告诉编译器说,我的 i 是随时可能变化的。
我现在爱着你,说不定下一秒就爱别人了。
因此编译器每次赋值都会从 i 的地址去找 i 的值。
这样就形成了对特殊地址的稳定访问。
十五、我来自远方—extern
extern是外面的,外来的意思。
这个关键字用于声明外部变量或者函数,表明变量或者函数的声明在别的文件中,这样编译器在寻找其定义时候就不会报错了。
举例:
我们在test.c文件中输入以下代码:
int a = 5;
新建文件qte.c输入以下代码:
extern int a;
int main()
{
printf("%d", a);
}
居然可以打印,这说明编译器通过这个extern的声明,找到了其他文件中的变量a.
十六、有容乃大—struct
结构体 (struct)是一种数据结构,可以包含很多数据类型,可以实现比较复杂的数据结构。
1.结构体的大小
常见的int,char类型变量,我们可以一眼看出占多少字节,但对于结构体,可就有点难度了。
结构体有多大?请点我。
2.柔性数组
C99中,结构体的最后一个元素允许时未知大小的数组,这就叫做柔性数组(flexible array)。
不懂柔性数组?请点我。
十七、我们联合—union
联合,又称为共用体。
联合是一种是一种特殊的自定义类型,包含一系列成员变量,特征是这些成员共用一块空间。
共用是如何体现的呢?
我们执行以下代码:
union Un
{
char c;//1
int i;//4
};
int main()
{
union Un u;
printf("%p\n", &u);//打印联合体地址
printf("%p\n", &(u.c));//打印c的地址
printf("%p\n", &(u.i));//打印i的地址
}
我们发现各成员的地址是一样的。
这说明联合确实具有“共用”的特点。
我们用图像来进一步理解。
1.联合实现判断大小端
运用联合体,我们可以编写以下程序判断编译器的存储模式
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int check_cls()
{
union Un
{
char c;
int i;
}u;
u.i = 1; //给i赋值1,此时c也被改变
return u.c; //若为小端,c为01
//若为大端,c为00
}
int main()
{
if (check_cls() == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
}
十八、枚举法天下无敌—enum
枚举顾名思义就是一一列举。
把可能的值一一列举。
比如我们现实生活中:
周一到周日,可以一一列举。
一年十二个月,可以一一列举。
如我们创建枚举变量Day,不进行赋值。
enum Day
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
};
若我们在enum里对周三赋值,即 Wednesday = 5;
则周四变为5后面一个数6,周五、周六同理。
十九、懒人必备—typedef
在编程中使用typedef目的一般有两个:
- 一个是给变量一个易记且意义明确的新名字
- 另一个是简化一些比较复杂的类型声明。