文章目录
- C语言关键字
- 我们人生中第一个C语言程序
- 变量的定义与声明
- 变量的作用域与生命周期
- 最宽宏大量的关键字 -- auto
- 最快的关键字 -- register(寄存器变量)
- 最名不符实的关键字 -- static
- 基本内置数据类型 -- char、short、int、long、float、double
- 最冤枉的关键字 -- sizeof
- signed、unsigned关键字(整形在内存中的存储)
- 这一节,我们就讲到这里了,下面一直会持续学习的,大家一起加油
- 努力提升自己,永远比仰望别人更有意义
C语言关键字
我们都知道C语言关键字有auto、if、int等等…但是我们知道C语言一共有多少个关键字呢?
一般来说,共有32个关键字(C90或C89标准);
但是后面C99又新增了5个关键字;但是目前主流的编译器,对C99好多并不支持;
所以,我们默认按照C90标准,即C语言共有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语言程序
//从vs中创建c语言项目
#include <stdio.h>
#include <windows.h>//此头文件仅仅是为了停屏
int main()
{
printf("hello world!\n");
system("pause");//pause停屏,我们可以看到终端
return 0;
}
在windows系统中,我们双击的本质就是运行程序,将程序添加到内存当中
任何程序在运行之前都必须加载到内存当中
注意: 程序在没有加载的时候,是放在硬盘当中的;我们之所以加载到内存当中 -》快;
变量的定义与声明
-
问题1:什么是变量?—在 内存中 开辟特定大小的空间,用来保存数据
-
问题2:如何定义变量:类型 变量名 = 默认值;例如:
int a = 10; double d = 3.14; char c = 'a';
-
问题3:为什么要定义变量?—
计算机的诞生就是为了解决人类计算能力不足的问题;即计算机就是为了计算的。
而计算,就需要数据;
我们计算数据,并不是无时无刻都需要计算;例如:我们吃饭并不是所有饭菜都立刻吃掉的,我们需要碗和盘子,把饭菜放到里面,想吃的时候就吃;
那么,有人就会抬杠:为什么要盘子,不可以直接使用在锅里吃吗?可以,但是 效率低
总之:我们定义变量是为了在我们想用的时候可以立即拿出,不用的时候就放在那就可以了;
- 问题4:变量定义的本质?— 我们现在知道了:程序运行之前,我们需要把数据加载到内存当中,程序计算时,需要使用到变量;那么, 定义变量的本质就是在内存中开辟一片空间,用来保存数据
- 问题5:变量声明的本质?— 我们举个例子:比如你和你的兄弟都喜欢一个女孩子翠花,但是你的胆子大,你就直接和翠花表白,翠花答应你了,这时候,你就可以告诉那个兄弟:翠花现在是我的女朋友,现在你们要保持距离。-》这里,表白就是定义,你和你兄弟说就是声明;声明的本质也就是广而告之
总结: 变量的定义:开辟空间的,定义只能定义一次(你不可能和翠花表白很多次,别人会被你吓跑的);变量的声明是告之,可以声明很多次;但是在声明的时候,我们就不可以进行赋值操作了(编译器会有可能把这个过程看成是定义过程)
变量的作用域与生命周期
- 作用域:指的是该变量可以被正常访问的代码区域(改变量的有效区域);这里我们需要知道:全局变量在整个程序运行期间都有效;局部变量只在本代码块内有效(用 {} 括起来的区域,就叫做代码块)
#include <stdio.h>
#include <windows.h>
int g_x = 100;//全局变量
int main()
{
int x = 10;
if (x)
{
int y = 20;//局部变量
//y只能在代码块内有效,而全局变量g_x在哪里都有效
printf("局部变量 y = %d, 全局变量 g_x = %d\n", y, g_x);
}
//printf("局部变量 y = %d\n", y);//错误,局部变量只在本代码块内有效
system("pause");
return 0;
}
- 生命周期:指的是改变量从定义到被释放的时间范围;全局变量:定义完成之后,程序运行的整个生命周期内,改变量一直有效;局部变量:进入代码块,形成局部变量,退出代码块,释放局部变量,生命周期结束;
- 作用域 VS 生命周期:例如:爱迪生对世界的贡献很大,爱迪生的生命周期就是从出生到死亡,而他并不是一直对世界作出贡献,他做出贡献的时间才是作用域
最宽宏大量的关键字 – auto
在c语言中,auto的用法很少且比较鸡肋,用处不大;但是在 C++ 中给auto赋予了很多的用处(配合STL);但是在C语言里面我们就暂且不说C++的知识了;
auto这个关键字一般是用来 修饰局部变量的 ,默认情况下都是auto修饰的(局部变量、自动变量、临时变量我们统称为局部变量)
#include <stdio.h>
#include <windows.h>
int main()
{
for (auto i = 1; i < 10; i++)
{
printf("i = %d\n", i);
if (1)
{
auto j = 0;//局部变量,这一个代码块结束之后,j又被赋值成0
printf("before: j = %d\n", j);
j += 1;
printf("after: j = %d\n", j);//j是0/1循环的
}
}
system("pause");
return 0;
}
注意:这里面的auto都是可以省略的,在C语言中我们可以忽略这个关键字
最快的关键字 – register(寄存器变量)
我们学习过计组的话,就会知道计算机的系统结构;其中,CPU主要负责计算相关的硬件单元,但是为了方便计算,(解决CPU与内存之间速度差异的问题)一般第一步需要把数据从内存中读取到CPU中,也就是需要CPU具有一定的存储能力;注意:CPU可能并不是需要计算当前读入到里面的数据,只是为了提前存储好,想用就可以用了,不然的话,速度太慢了;
我们来看一下CPU存储金字塔:
距离CPU越近的存储硬件,速度越快
- 问题1:什么是寄存器? – 如果我们学过计组的话,就会很清楚的明白,但是如果我们还没有学的时候,我们只需知道CPU内部集成了一组存储硬件,这组硬件叫做寄存器即可
- 问题2:寄存器存在的本质? – 提高计算机的运行效率,就不需要从内存中读取数据了
- 问题3:什么样的变量适合使用寄存器变量呢?
- 局部的(全局变量会导致CPU寄存器被长时间占用)
- 不会被写入的(写入需要回内存,后续还要读取检测的话,register就没有意义了)
- 高频率读取的(提高效率)
- 寄存器数量有限,不可以大量使用register关键字
注意:除了以上的内容,还有就是:register修饰的变量不可以取地址(地址是内存相关的概念)
demo:
#include <stdio.h>
#include <windows.h>
int main()
{
register int a = 10;
printf("&a = %p\n", &a);//报错 -- C2103 寄存器变量上的“& ”
system("pause");
return 0;
}
最名不符实的关键字 – static
在讲这个关键字之间,我们之前应该了解到,我们自己在写C语言项目的时候,项目会非常大且复杂,我们一般都是会写很多了源文件(.c文件)和一个头文件(.h);我们把所有用到的函数、变量、头文件、#define、struct、typedef等等都会放到 .h(头文件) 文件中,这样可以大大减少项目的维护成本;这里面又会牵扯到头文件重复包含的问题,我们先给一个解决方案,后面还会细讲
#pragma once
我们先提一下extern这个关键字,当我们想跨源文件来使用某个变量或函数的时候,就必须使用到这个关键字例如:extern int g_val;
函数可以直接在头文件声明,不需要使用 extern,但是最好也带上extern void show();
总结:变量声明必须带上extern,函数声明建议带上extern(变量不可以跨文件访问,而函数可以跨文件访问)
static的作用
- 修饰全局变量的时候,该全局变量只能在本文件内被使用,不可跨文件访问,也就是更改变量的作用域
- 修饰函数的时候,该函数只能在本文件内被使用,不可跨文件访问,也就是更改变量的作用域
- 修饰局部变量的时候,会更改局部变量的生命周期(不是作用域)
demo:
#include <stdio.h>
#include <windows.h>
void fun1()
{
int i = 0;
i++;
printf("no static: i = %d\n", i);//输出10个1
}
void fun2()
{
static i = 0;
i++;
printf("has static: i = %d\n", i);//输出1-10;更改了i的生命周期
}
int main()
{
for (int i = 0; i < 10; i++)
{
//fun1();
fun2();
}
system("pause");
return 0;
}
总结:static修饰局部变量,变量的生命周期会变成全局周期(作用域不变)
补充一下C语言寻址空间的知识
基本内置数据类型 – char、short、int、long、float、double
基本数据类型有以下几种:
我们这里先讲解基本的内置类型:
这里,我们只需要搞懂一个问题:为什么语言要有好多种数据类型,直接将内存整体使用不好吗?
在任何时候,都不是只有我们当前的程序在运行,还有其他很多种程序在运行,你把这块内存占用了,其他的程序并不知道你正在使用这块内存,他不知道是不是可以使用;还有,你把这块内存全用了,你的程序并不是无时无刻都在使用,当不需要使用的时候,你就是在浪费这块内存了;
做月饼的例子:月饼的外表有各种各样的,我们只需要使用不同的模子就可以制造出各种各样的月饼了;
所以,C语言中,为什么有那么多的类型,就是为了满足不同的场景
比如:我们想计算我们的工资的时候,4个字节的int就足够了,没有必要开大
那么,我们这里就需要知道,这些类型到底在内存中占用多少内存大小呢?
注意:上面的单位都是字节
最冤枉的关键字 – sizeof
我们为什么称sizeof为最冤枉的关键字?就是因为常年别人都把他误认为是函数,这是错误的,sizeof是C语言中的关键字
我们只需要记住sizeof是计算类型或变量在内存中占多大空间的就可以了,上面我们已经计算过了,至于sizeof与指针的内容,我们会在后面指针的部分讲到;
注意1:
int a = 10;
printf("%d\n", sizeof(a));//对
printf("%d\n", sizeof a);//对
printf("%d\n", sizeof(int));//对
printf("%d\n", sizeof int);//错误
注意2:sizeof后面的括号里是不允许有其他操作的,就算写也不起效果
int a = 10;
printf("%d\n", sizeof(a++));
printf("%d\n", a);//a还是10
signed、unsigned关键字(整形在内存中的存储)
浮点数(float、double)都是有符号的,浮点数没有unsigned
我们需要先理解原码、反码、补码的概念:
- 计算机中的有符号数有三种表示方法,原码、反码、补码
- 三种表示方法均有符号位与数值位,符号位都是用0表示正,1表示负,位数值位的表示各不相同
- 正数与无符号数(unsigned)的原码、反码、补码都相同
- 负数原码:直接将二进制按照正负数的形式翻译成二进制就可以了
- 负数反码:将原码的符号位不变,其他为按位去反就可以了
- 负数补码:反码+1就得到补码
- 对于整形来说:数据存放内存中其他存放的是补码(后面会讲)
我们知道,一个变量的创建是现在内存中开辟空间的,空间的大小是由类型决定的
那么,数据开辟好了空间,在内存中到底是怎样存储的呢?
我们在定义类型的时候,默认前面省略了 signed
我们现在来看个问题?unsigned int b = -10;
这个写法是否正确呢?
答案?这样写是没有问题的,是正确的的;整形在存储的时候,是不关心内容的
我们来看下面这个例子:
我们a虽然是有符号的整形,但是我们取出来的时候,按照有符号的整形打印他就是-10,而我们把它当成无符号的整形取出来的时候,就是那个值了;同理b;所以我们发现:我们定义变量,给他类型,只是给他开辟了特定的空间,到底存放什么内容我们不用管,但是取出来的时候,我们就必须看他是什么类型,或者你按照什么类型给他取出来的
下面还有几个小的知识点,我们来看看吧~
- 我们知道了原码怎么转向补码的,但是我们怎么反过来把补码转成原码呢?
- 二进制快速转化口诀:
到这里,我们已经知道了我们的数据是存放到内存里面,按照补码形式进行存储的,那么我们来看一下到底是怎样存的呢?
我们以int a = 10;
来看一下
我们发现在vs中,我们的变量最后在内存中是这样存储的,我们来分析一下?
10的补码是:0000 0000 0000 0000 0000 0000 0000 1010
在计算中存储的是十六进制:0x00 00 00 0A
为什么我们在内存中看到的是反过来的呢?0x0a 00 00 00
这里我们需要了解大小端的概念:我们定义好变量后,会开辟一片空间,将变量的二进制补码写入到内存中,那么我们如何写进去呢?是从前往后写,还是从后往前写呢?这里没有特定的要求,不管我们怎么放,只要使用同等的条件去取就可以了?由此,产生了两种不同的厂商,分为大端和小端,目前比较流行的是小端。
对于大小端这件事,对于每个人是无法达到一致的,每个人都有每个人的看法,我们都没有足够的理由说服对方;
对于数据的存与取,我们除了要看上面我们学的看数据类型,这里还要加一个看是使用大端存储,还是小端存储不管哪种存储方式,我们只要使用同等条件去取,都可以!
我们怎么判别我们电脑使用大端还是小端呢?(2015年百度一道笔试题)
#include <stdio.h>
#include <windows.h>
int check_sys()
{
int i = 1;
return (*(char*)&i);
}
int main()
{
int ret = check_sys();
if (ret == 1) printf("小端\n");
else printf("大端\n");
system("pause");
return 0;
}
上面输出的结果是小端;我们来解释一下return (*(char*)&i);
这一段代码:我们知道 i 放到内存中的十六进制数是:0x01 00 00 00;我们只需要看 i 的第一个字节内容,如果是1就是小端,如果是0就是大端;那么我们怎么才可以拿到 i 的第一个字节呢?char* 的指针正好就是访问一个字节的地址,我们完整的代码就是:
int i = 1;
char* p = (char*)&i;
if(*p == 1) printf("小端\n");
else printf("大端\n");
为什么变量采用的都是补码来进行存储?
- 在计算机系统中,数值一律采用补码来表示和存储。原因在于:使用补码,可以将符号位和数值位统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)。此外,补码和原码的相互转换,其运算过程是相同的,不需要额外的硬件电路。
整形取值范围(很重要)
简单为例,我们就以 signed char 为例,其它的和这类似!
那么为什么 1000 0000 就表示成-128呢?我们来看看char c = -128;
在内存中表示多少
我们可以发现char类型是可以存放-128这个数据的,而128就不可以,这是为什么呢?
我们可以总结一下各种数据类型的取值范围:
- char: [-2 ^ 7, 2 ^ 7 - 1]
- short: [-2 ^ 15, 2 ^ 15 - 1]
- int: [-2 ^ 31, 2 ^ 31 - 1]
好了,到这里我们整形数据存储的知识就学的就差不多了,我们来做几个题目检验一下成果吧!
例题一:
#include <stdio.h>
#include <windows.h>
int main()
{
char a[1000];
for (int i = 0; i < 1000; i++) a[i] = -1 - i;
printf("%d\n", strlen(a));
system("pause");
return 0;
}
第一点:我们首先要知道 strlen 这个库函数是计算字符串长度的,遇到 ‘\0’ 截至,并且 ‘\0’ 不计算在长度内;'\0’对应的ASCII码值就是0。例如:char a[5] = {3, ‘h’, 5, 0, ‘e’};则 strlen(a)的答案就是3,只计算0前面的字符串长度,且0不包括在内。
第二点:我们需要知道当变量取值范围超过最大值时,之后的值该是多少?
因此,这道题的答案就是255!
例题二:
#include <stdio.h>
#include <windows.h>
int main()
{
int i = -20;
unsigned int j = 10;
printf("%d\n", i + j);
system("pause");
return 0;
}
例题三:
#include <stdio.h>
#include <windows.h>
int main()
{
unsigned int i;
for (i = 9; i >= 0; i--) printf("%u\n", i);
system("pause");
return 0;
}
这个题我们要注意:i是个无符号整形变量,打印的时候,会依次打印9 、8、7、6、5…0,当打印到0的时候,i 再减1,就相当于 i + (-1);也就是:
这个数按照 unsigned int 来打印的话就是 2 ^ 32 - 1 那么大
其实变量的取值范围就是按照之前我给大家画的那个圆来记,以 signed char 来说:
当变量取到头的时候,会再循环一圈,以此类推…
还有我们在定义unsigned类型的时候,最后最好加个 u,就像定义 float类型在后面加个 f一样,例如:unsigned int u = 10u;
还有一点,大家在这里的时候,记得与整形提升不要搞混淆
c前面的 c + 1会把char类型的变量整形提升到 int 型,再进行加法运算
整形提升:在表达式计算时,各种整形首先要提升为 int 类型;表达式的整形运算要在CPU的相应器件内进行,CPU运算器的操作数的字节长度一般是 int 类型的字节长度