关键字深度剖析
内容方面:这本书并不是一本好书,因为书中存在很多错误。
编写思路:从编写思路上来看,它是一本好书。它用最少的篇幅,能够明确地涵盖C语言中的大部分重点内容。
学习建议:要学完这本书,需要做到以下几点:
找到书中所有的错误。
补充大量的内容。
通过读书,系统地学习相关知识。
关键字 - 第一讲
目标
初步了解关键字的分类。
深刻理解变量的概念。
深刻理解定义与声明的区别。
理解
auto
关键字。从存储结构角度理解
register
关键字。
关键字分类
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 | 子程序返回语句(可以带参数,也可不带参数) |
第一个C程序 - 补充内容
//1. vs2022中建立项目
//2. 编写第一个C程序"hello world"
#include <stdio.h>
#include <windows.h> //windows.h系统头文件,仅仅是为了停屏
int main()
{
printf("hello world!\n");
system("pause"); //pause停屏,暂时不讲,会用就行
return 0;
}
//运行程序的方式,当然可以用vs直接启动
//当然,也可以在vs项目中,找到代码生成的二进制可执行程序,双击即可。
//所以:我们的角色是写代码,编译器的角色是把文本代码变成二进制可执行程序。
//双击?不就是windows下启动程序的做法吗?
//那么启动程序的本质是什么呢?将程序数据,加载到内存中,让计算机运行!
//那么为什么要加载到内存中呢?这个在Linux专题有详细的说明
我们将文本代码转化成为可执行程序,可执行程序就是二进制文件,在Windows当中我们是可以通过双击来启动程序的!其本质就是将程序加载到内存当中!任何程序在运行之前都要从硬盘加载到内存当中!
加载到内存中是由冯诺依曼体结构过决定的!
因为CPU访问内存超级快,所以程序/数据要加载到内存中!!!
局部变量:包含在代码块中的变量,具有临时性。进入代码块时自动形成,退出代码块时自动释放。
全局变量:在所有函数外定义的变量,具有全局性,整个程序运行期间都有效。
代码块:用{}
括起来的区域称为代码块。
#include <stdio.h>
#include <windows.h>
int g_x = 100; // 全局变量
int main()
{
int x = 10; // 局部变量
printf("x:%d\n", x);
system("pause");
return 0;
}
定义与声明 - 补充内容
什么是变量(是什么)
在内存中开辟特定大小的空间,用来保存数据
关键字:内存
如何定义变量(怎么用)
int x = 10;
char c = 'a';
double d = 3.14;
类型 变量名 = 默认值
为什么要定义变量(为什么)
计算机是为了解决人计算能力不足的问题而诞生的。即,计算机是为了进行计算的。而计算,就需要数据。而要计算,任何一个时刻,不是所有的数据都要立即被计算。如同:要吃饭,不是所有的饭菜都要立即被你吃掉。饭要一口一口吃,那么你还没有吃到的饭菜,就需要暂时放在盘子里。这里的盘子,就如同变量,饭菜如同变量里面的数据。
换句话说,为何需要变量?因为有数据需要暂时被保存起来,等待后续处理。
那么,为什么吃饭要盘子?我想吃一口菜了,直接去锅里找不行吗?当然行,但是效率低。因为我们吃饭的地方,和做饭的地方,是比较"远"的。
变量定义的本质
我们现在已知:
程序运行,需要加载到内存中
程序计算,需要使用变量
那么,定义变量的本质:在内存中开辟一块空间,用来保存数据。(为何一定是内存:因为定义变量,也是程序逻辑的一部分,程序已经被加载到内存)
变量声明的本质
声明就是一种告知,告知这个变量是在某个地方已经定义好了,等后期编译链接就可以用到了。代码我们暂时不写,等到后面讲extern的时候,在细谈。
定义只能定义一次!
声名是可以多次的!
1.1 最宽宏大量的关键字 - auto
变量的分类 - 补充内容
局部变量:包含在代码块中的变量叫做局部变量。局部变量具有临时性。进入代码块,自动形成局部变量,退出代码块自动释放。[网上很多说函数中的变量是局部变量,不能说错,但说法是不准确的]
全局变量:在所有函数外定义的变量,叫做全局变量。全局变量具有全局性。
代码块:用{}括起来的区域,就叫做代码块
#include <stdio.h>
#include <windows.h>
int g_x = 100; //全局变量
int main()
{
int x = 10; //局部变量,main函数也是函数,也有代码块{}
printf("x:%d\n", x);
system("pause");
return 0;
}
变量的作用域 - 补充内容
作用域概念:指的是该变量的可以被正常访问的代码区域
#include <stdio.h>
#include <windows.h>
int main()
{
int x = 10;
if (x == 10){
int y = 20;
printf("局部: x: %d, y: %d\n", x, y);//只能在本代码块内有效
}
printf("局部: x: %d, y: %d\n", x, y);//报错,y不能被访问
system("pause");
return 0;
}
局部变量:只在本代码块内有效!!!
全局变量:整个程序运行期间,都有效!
#include <stdio.h>
#include <windows.h>
int g_x = 100; //全局变量
void show()
{
printf("show: 全局: %d\n", g_x); //在任何代码块中都可以被访问
}
int main()
{
show();
printf("main: 全局: %d\n", g_x); //在任何代码块中都可以被访问,甚至被修改
system("pause");
return 0;
}
//
#include <stdio.h>
#include <windows.h>
int g_x = 100; //全局变量
int main()
{
int g_x = 10; //局部变量,与全局同名
printf("g_x:%d\n", g_x); //输出的是局部,也就是局部和全部同名的时候,优先局部。所以,强烈不建议这样干。
system("pause");
return 0;
}
变量的生命周期 - 补充内容
生命周期概念:指的是该变量从定义到被释放的时间范围,所谓的释放,指的是曾经开辟的空间"被释放"
局部变量:进入代码块,形成局部变量[开辟空间],退出代码块,"释放"局部变量
全局变量:定义完成之后,程序运行的整个生命周期内,该变量一直都有效
通过上面的例子,我们能感知一二,不过真正说清楚,需要等函数讲完,在谈一下。尽管,上面理解也没问题。
作用域 vs 生命周期
深刻理解两者的区别:
作用域(Scope)指的是变量、函数或标识符在代码中的可见性和可访问性范围。
生命周期(Lifetime)指的是变量或对象从创建到销毁的整个存在时间。
特性 | 作用域(Scope) | 生命周期(Lifetime) |
---|---|---|
关注点 | 空间维度:代码的哪些部分可以访问 | 时间维度:变量存在的时间段 |
决定因素 | 代码结构(如函数、块) | 存储类别(如自动、静态、动态) |
主要问题 | "在哪里可以访问这个变量?" | "这个变量何时创建和销毁?" |
auto 相关
如何使用:一般在代码块中定义的变量,即局部变量,默认都是auto修饰的,不过一般省略
默认的所有变量都是auto吗?不是,一般用来修饰局部变量
中断一下:后面我们听到的,局部变量,自动变量,临时变量,都是一回事。我们称局部变量
#include <stdio.h>
#include <windows.h>
int main()
{
for (int i = 0; i < 10; i++) {
printf("i=%d\n", i);
if(1)
{
auto int j = 0; //自动变量
printf("before: j=%d\n", j);
j += 1;
printf("after : j=%d\n", j);
}
}
system("pause");
return 0;
}
思考:用auto修饰可以吗?去掉j的auto可以吗?
结论:完全可以,已经很多基本永不使用。
C语言中的auto
关键字
含义:
表示"自动存储期"的变量(默认的存储类别)
是C语言四种存储类别说明符之一(
auto
,register
,static
,extern
)
特点:
默认情况下,函数内定义的变量都是
auto
的极少显式使用,因为不加存储类别说明符的局部变量默认就是
auto
变量生命周期限于其所在的作用域(函数/块执行期间)
void func() {
auto int x = 5; // 显式使用auto(完全等同于不加auto)
int y = 10; // 隐式auto
}
这个和我们的C++11的auto不同哦:C++11的auto
类型推导:让编译器根据初始化表达式自动推导变量类型
完全改变了原有
auto
关键字的含义
1.2 最快的关键字 - register
其实,CPU主要是负责进行计算的硬件单元,但是为了方便运算,一般第一步需要先把数据从内存读取到CPU内,那么也就需要CPU具有一定的数据临时存储能力。注意:CPU并不是当前要计算了,才把特定数据读到CPU里面,那样太慢了。
所以现代CPU内,都集成了一组叫做寄存器的硬件,用来做临时数据的保存。
存储金字塔
全部集成在现代CPU内部
距离CPU越近的存储硬件,速度越快。
寄存器的认识
当前,各位同志可以不关系硬件细节,只要知道CPU内集成了一组存储硬件即可,这组硬件叫做寄存器。
寄存器存在的本质
在硬件层面上,提高计算机的运算效率。因为不需要从内存里读取数据啦。
register 修饰变量
尽量将所修饰变量,放入CPU寄存区中,从而达到提高效率的目的
那么什么样的变量,可以采用register呢?
局部的(全局会导致CPU寄存器被长时间占用)
不会被写入的(写入就需要写回内存,后续还要读取检测的话,register的意义在哪呢?)
高频被读取的(提高效率所在)
如果要使用,请不要大量使用,因为寄存器数量有限
这里除了上面的,再有一点,就是register修饰的变量,不能取地址(因为已经放在寄存区中了嘛,地址是内存相关的概念)
#include <stdio.h>
#include <windows.h>
int main()
{
register int a = 0;
//a = 200;//可以被写入,但是register的意义就没了!
printf("%a = %p\n", &a);
//编译器报错:错误 1 error C2103:寄存器变量上的"&"
//注意,这里不是所有的编译器都报错,目前我们的vs2013是报错的。
//鉴于有些同志刚开始学,不给大家增加负担,我们后面再慢慢引入另一个编译器。
system("pause");
return 0;
}
我的意见:该关键字,不用管,因为现在的编译器,已经很智能了,能够进行比人更好的代码优化。早期编译器需要人为指定register,来进行手动优化,现在不需要了。