1 计算机基本结构
约翰·冯·诺依曼提出了计算机制造的三个基本原则,即采用二进制逻辑、程序存储执行以及计算机由五个部分组成(运算器、控制器、存储器、输入设备、输出设备)。
1.1 CPU
CPU的主频:粗略的说,CPU的主频就是时钟的震荡的每秒次数,可近似的看作每秒执行的指令数。简单的说就是主频决定了CPU的运算速度。
1.1.1 逻辑门
电子开关-机械继电器(Mechanical Relay),通过电子开关,我们可以实现 1 位(bit) 的逻辑运算。
1.1.2 门电路
可以实现 1 位(bit) 的基本逻辑运算。
1) 非门(NOT GATE):输入为 true 输出 false,输入false 则输出 true;
2) 与门(AND GATE):两个都为 true才输出true,否则输出false;
3) 或门(OR GATE):一个为 1则为1,两个为 false 则为false;
4) 异或门(XOR GATE):通过非门与门和或门相结合就形成了异或门,相同为 false,不相同则为 true
1.1.3 算数逻辑单元ALU(Arithmetic & Logic Unit)
ALU 是计算机中进行算数、逻辑运算的核心部件。
1.1.3.1 算数单元
- 半加器:半加器就是通过一个与门和异或门,来计算两个 1位(bit)数的相加,既要考虑到和也要考虑到进位;
- 全加器:全加器是通过两个半加器进行 3个1位(bit)的数 进行运算,同样要考虑计算出来的和是否要进位;
- 加法器:通过1个半加器和7个全加器就能组成一个能计算 8个位 1位(bit) 的全加器。
1.1.3.2 逻辑单元
逻辑单元主要用来进行逻辑操作,最基本的操作就是 与、或、非操作,但不只是一位(bit)数的比较
1.2 存储器
- 主存储器即内存:程序中待处理的数据和处理的结果都存储在内存中;
- 外存储器:用来长期保存数据的大容量存储器;
- 寄存器是: CPU 内部的高速存储器,速度快,数目少;
1.3 操作系统OS
操作系统是一个"搞管理" 的软件管理硬件设备与软件资源(包括文件、进程),管理指描述和组织。
1.3.1 进程
进程是操作系统中非常核心的一个概念,进程其实是计算机完成工作的一个"过程",CPU的核数相当于CPU的分身数,电脑上的多任务系统其实就是基于进程调度这样的机制来完成的。
并发式执行:1个CPU运行多个进程,由于CPU的运行速度极快,虽然CPU在一直进行切换,但是咱们坐在电脑前的用户,是感知不到这个切换的过程的。
并行式执行:多个CPU,运行多个进程,进程1和进程2无论是微观还是宏观,都是同时执行的。
1.3.2 进程的属性
1)进程的状态:
-
- 运行状态:进程正在运行;
- 就绪状态:进程已经做好准备,随时准备被CPU调度执行;
- 阻塞状态:进程在此状态不能执行,只有等阻塞该进程的事件完成后才能执行,比如编程时等待我们输入;
2) 进程的优先级:给进程安排不同的优先级,优先级越高的进程,更容易被CPU调度执行。
3) 进程的上下文:进程的上下文,主要是存储调度出CPU之前寄存器中的信息(把寄存器信息保存到内存中),等到这个进程下次恢复到CPU上执行的时候,就把内存保存好的数据恢复到寄存器中。(进程本身是感知不到自己啥时候被调度出CPU的);
4)进程的记账信息:记录进程在CPU上执行了多久了,用来辅助决定这个进程是继续执行,还是要调度出CPU了。
1.3.3 虚拟地址空间
一个进程想要运行,就需要给它分配一些系统资源,其中内存就是一个最核心的资源,物理地址就是真实的内存的地址。
虚拟地址空间:每个进程所能访问的内存地址范围,是一段连续的虚拟内存空间。
好处就是让进程之间独立性提高了,不至于相互影响,整个系统更加稳定;缺点是两个进程需要相互配合的时候,进程间通信变难,通信需要使用一些特殊的手段,比如:文件,管道(是内核中提供的一个队列),消息队列,信号量等。
2 C语言知识
2.1 数据类型
送入计算机的数字,字母,符号等信息必须转换成 0、 1 组合的数据形式才能被计算机识别。能够进行算术运算得到明确数值概念的信息成为计算机数值数据,其余的信息成为非数值数据。
数值数据类型:包括十进制、二进制、十六进制(0x)和八进制(0);
非数值数据类型:非数值数据包括文字、符号、图像、语言和逻辑信息等,也都是以 0 、 1 形式存在。字符数据在机器内也被变换成二进制编码的形式。国际上普遍采用的一种编码是美国国家信息交换标准代码,简称为 ASCII 码。
C语言中基本数据类型如下:
-
- 整数类型:short int、int、long int、long long int
- 浮点数类型:float、double、long double
- 字符类型:char
- 布尔类型:_Bool或bool(定义时需要引用stdbool.h头文件)
- 枚举类型:enum
2.1.1 比特位
CPU能读懂的最小单位是:比特位,bit,b。每个比特位只能存放二进制数,即0和1.
2.1.2 字节
内存机构最小寻址单位:字节,Byte,B,注:1Byte = 8 bit
2.1.3 符号位
存放signed类型的存储单元中,左边第一位表示符号位。如果该位为0,表示该整数是一个正数;如果该位为1,表示该整数是一个负数。一个32位的整型变量,除去左边第一位符号位,剩下表示值的只有31个比特位。
2.1.4 补码
数据在计算中以二进制补码的形式进行存储:
-
- 正数的补码是该数的二进制形式;
- 负数的补码需要通过以下几步获得:先取得该数的绝对值的二进制形式,再将第1步的值按位取反(除符号位),最后将第2步的值加1。
2.1.5 整型类型
标准整数类型的存储大小和值范围的细节:
类型 | 存储大小 | 值范围 |
char | 1 字节 | -128 到 127 或 0 到 255 |
unsigned char | 1 字节 | 0 到 255 |
signed char | 1 字节 | -128 到 127 |
int | 2 或 4 字节 | -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647 |
unsigned int | 2 或 4 字节 | 0 到 65,535 或 0 到 4,294,967,295 |
short | 2 字节 | -32,768 到 32,767 |
unsigned short | 2 字节 | 0 到 65,535 |
long | 4 字节 | -2,147,483,648 到 2,147,483,647 |
unsigned long | 4 字节 | 0 到 4,294,967,295 |
2.1.6 浮点类型类型
标准浮点类型的存储大小、值范围和精度的细节:
类型 | 存储大小 | 值范围 | 精度 |
float | 4 字节 | 1.2E-38 到 3.4E+38 | 6 位有效位 |
double | 8 字节 | 2.3E-308 到 1.7E+308 | 15 位有效位 |
long double | 16 字节 | 3.4E-4932 到 1.1E+4932 | 19 位有效位 |
2.1.7 void类型
void 类型指定没有可用的值。它通常用于以下三种情况下:
-
- 函数返回为空:C 中有各种函数都不返回值,或者您可以说它们返回空。不返回值的函数的返回类型为空。例如:void exit (int status);
- 函数参数为空:C 中有各种函数不接受任何参数。不带参数的函数可以接受一个 void。例如 int rand(void);
- 指针指向 void:类型为 void * 的指针代表对象的地址,而不是类型。例如,内存分配函数 void *malloc( size_t size ); 返回指向 void 的指针,可以转换为任何数据类型。
2.1.8 类型转换
类型转换是将一个数据类型的值转换为另一种数据类型的值。C 语言中有两种类型转换:
-
- 隐式类型转换:隐式类型转换是在表达式中自动发生的,无需进行任何明确的指令或函数调用。它通常是将一种较小的类型自动转换为较大的类型,例如,将int类型转换为long类型或float类型转换为double类型。隐式类型转换也可能会导致数据精度丢失或数据截断。
- 显式类型转换:显式类型转换需要使用强制类型转换运算符(type casting operator),它可以将一个数据类型的值强制转换为另一种数据类型的值。强制类型转换可以使程序员在必要时对数据类型进行更精确的控制,但也可能会导致数据丢失或截断。
2.2 变量
2.2.1 变量的声明
变量在程序中使用时 , 必须预先说明它们的存储类型和数据类型。
变量说明的一般形式是:
< 存储类型 > < 数据类型 > < 变量名 > ;
-
-
- < 存储类型 > 是关键词 auto 、 register 、 static 和 extern;
- < 数据类型 > 可以是基本数据类型,也可以是自定义的数据类型;
- < 变量名 > 变量名由字母、数字、下划线组成,不能以数字开头,不能和 C 的关键字重名;
-
2.2.2 变量的存储类型
变量的存储类型分为auto 、 register 、 static 和 extern四类:
-
- auto(自动存储):修饰的变量存储在堆栈区,习惯上省略不写,如没有其他存储类型修饰则默认为自动存储;auto型的局部变量随函数调用而生成,函数调用结束而释放。如修饰的变量不进行初始化,则系统会随机给一个值。(auto) int i;
- static(静态存储):修饰的局部变量用固定地址存储在数据区,函数运行结束不会被释放,直至程序结束才会释放,因此有记忆功能,每次调用都保留上一次操作的值;修饰的全局变量只能被本文件内的函数使用,其他源文件不可访问。若修饰的变量不进行初始化,则系统默认赋为0。static int i;
#include <stdio.h>
/* 函数声明 */
void func1(void);
static int count=10; /* 全局变量 - static 是默认的 */
int main()
{
while (count--) {
func1();
}
return 0;
}
void func1(void)
{
/* 'thingy' 是 'func1' 的局部变量 - 只初始化一次
每次调用函数 'func1' 'thingy' 值不会被重置。*/
static int thingy=5;
thingy++;
printf(" thingy 为 %d , count 为 %d\n", thingy, count);
}
-
- extern(外部存储):用来修饰全局变量,修饰的变量是其他的源文件的全局变量,表示在该文件下引用了其他源文件定义的全局变量,extern声明不是定义,即不分配存储空间,但用static修饰的全局变量不可被其他文件访问。extern int i;
#include <stdio.h>
int count ;
extern void write_extern();
int main()
{
count = 5;
write_extern();
}
#include <stdio.h>
extern int count;
void write_extern(void)
{
printf("count is %d\n", count);
}
-
- register(寄存器存储):修饰的局部变量存储在寄存器中,若变量被频繁使用,则可缩短变量的操作时间,加快程序的运行速度。一般修饰的变量不能太多且数据长度应该小于或等于整数的长度,因为寄存器数量有限且主要工作不是用来存储的,定义多了系统会自动把多出的register变量当做auto处理。只能应用于auto变量或者函数的形参,且不能用&取地址。register int i;
2.2.3 变量初始化注意事项
在 C 语言中,如果变量没有显式初始化,那么它的默认值将取决于该变量的类型和其所在的作用域。对于全局变量和静态变量(在函数内部定义的静态变量和在函数外部定义的全局变量),它们的默认初始值为零。以下是不同类型的变量在没有显式初始化时的默认值:
-
- 整型变量(int、short、long等):默认值为0。
- 浮点型变量(float、double等):默认值为0.0。
- 字符型变量(char):默认值为'\0',即空字符。
- 指针变量:默认值为NULL,表示指针不指向任何有效的内存地址。
- 数组、结构体、联合等复合类型的变量:它们的元素或成员将按照相应的规则进行默认初始化,这可能包括对元素递归应用默认规则。
需要注意的是,局部变量(在函数内部定义的非静态变量)不会自动初始化为默认值,它们的初始值是未定义的(包含垃圾值)。因此,在使用局部变量之前,应该显式地为其赋予一个初始值。
2.3 常量
常量可以是任何的基本数据类型,比如整数常量、浮点常量、字符常量,或字符串字面值,也有枚举常量。常量的值在定义后不能进行修改。常量可以直接在代码中使用,也可以通过定义常量来使用。
2.3.1 整型常量
整数常量可以是十进制、八进制或十六进制的常量。前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制。
整数常量也可以带一个后缀,后缀是 U 和 L 的组合,U 表示无符号整数(unsigned),L 表示长整数(long)。后缀可以是大写,也可以是小写,U 和 L 的顺序任意。
2.3.2 浮点常量
浮点常量由整数部分、小数点、小数部分和指数部分组成。您可以使用小数形式或者指数形式来表示浮点常量。
-
- 当使用小数形式表示时,必须包含整数部分、小数部分,或同时包含两者。
- 当使用指数形式表示时, 必须包含小数点、指数,或同时包含两者。带符号的指数是用 e 或 E 引入的。
2.3.3 字符常量
字符常量是括在单引号中,例如,'x' 可以存储在 char 类型的简单变量中。字符常量可以是一个普通的字符(例如 'x')、一个转义序列(例如 '\t'),或一个通用的字符(例如 '\u02C0')。
在 C 中,有一些特定的字符,当它们前面有反斜杠时,它们就具有特殊的含义,被用来表示如换行符(\n)或制表符(\t)等。下表列出了一些这样的转义序列码:
转义序列 | 含义 |
\\ | \ 字符 |
\' | ' 字符 |
\" | " 字符 |
\? | ? 字符 |
\a | 警报铃声 |
\b | 退格键 |
\f | 换页符 |
\n | 换行符 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ooo | 一到三位的八进制数 |
\xhh . . . | 一个或多个数字的十六进制数 |
2.3.4 字符串常量
字符串字面值或常量是括在双引号 " " 中的。一个字符串包含类似于字符常量的字符:普通的字符、转义序列和通用的字符。可以使用空格做分隔符,把一个很长的字符串常量进行分行。字符串常量在内存中以 \0 结尾。
2.3.5 定义常量
在 C 中,有两种简单的定义常量的方式:
-
- 使用 #define 预处理器: #define 可以在程序中定义一个常量,它在编译时会被替换为其对应的值。用法:#define 常量名 常量值
- 使用 const 关键字:const 关键字用于声明一个只读变量,即该变量的值不能在程序运行时修改。const 声明常量要在一个语句内完成定义和初始化赋值。
#define 与 const 这两种方式都可以用来定义常量,通常情况下,建议使用 const 关键字来定义常量,因为它具有类型检查和作用域的优势,而 #define 仅进行简单的文本替换,可能会导致一些意外的问题。#define 预处理指令和 const 关键字在定义常量时的区别:
-
- 替换机制:#define 是进行简单的文本替换,而 const 是声明一个具有类型的常量。#define 定义的常量在编译时会被直接替换为其对应的值,而 const 定义的常量在程序运行时会分配内存,并且具有类型信息。
- 类型检查:#define 不进行类型检查,因为它只是进行简单的文本替换。而 const 定义的常量具有类型信息,编译器可以对其进行类型检查。这可以帮助捕获一些潜在的类型错误。
- 作用域:#define 定义的常量没有作用域限制,它在定义之后的整个代码中都有效。而 const 定义的常量具有块级作用域,只在其定义所在的作用域内有效。
- 调试和符号表:使用 #define 定义的常量在符号表中不会有相应的条目,因为它只是进行文本替换。而使用 const 定义的常量会在符号表中有相应的条目,有助于调试和可读性。
2.4 运算符
运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。C 语言内置了丰富的运算符,并提供了以下类型的运算符:算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符、杂项运算符。
2.4.1 算术运算符
下表显示了 C 语言支持的所有算术运算符。假设变量 A 的值为 10,变量 B 的值为 20,则:
运算符 | 描述 | 实例 |
+ | 把两个操作数相加 | A + B 将得到 30 |
- | 从第一个操作数中减去第二个操作数 | A - B 将得到 -10 |
* | 把两个操作数相乘 | A * B 将得到 200 |
/ | 分子除以分母 | B / A 将得到 2 |
% | 取模运算符,整除后的余数 | B % A 将得到 0 |
++ | 自增运算符,整数值增加 1 | A++ 将得到 11 |
-- | 自减运算符,整数值减少 1 | A-- 将得到 9 |
2.4.2 关系运算符
下表显示了 C 语言支持的所有关系运算符。假设变量 A 的值为 10,变量 B 的值为 20,则:
运算符 | 描述 | 实例 |
== | 检查两个操作数的值是否相等,如果相等则条件为真。 | (A == B) 为假。 |
!= | 检查两个操作数的值是否相等,如果不相等则条件为真。 | (A != B) 为真。 |
> | 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 | (A > B) 为假。 |
< | 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 | (A < B) 为真。 |
>= | 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 | (A >= B) 为假。 |
<= | 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 | (A <= B) 为真。 |
2.4.3 逻辑运算符
下表显示了 C 语言支持的所有关系逻辑运算符。假设变量 A 的值为 1,变量 B 的值为 0,则:
运算符 | 描述 | 实例 |
&& | 逻辑与运算符。如果两个操作数都非零,则条件为真。 | (A && B) 为假。 |
|| | 逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。 | (A || B) 为真。 |
! | 逻辑非运算符。如果条件为真则逻辑非运算符将使其为假。 | !(A && B) 为真。 |
2.4.4 位运算符
位运算符作用于位,并逐位执行操作。下表显示了 C 语言支持的位运算符。假设变量 A 的值为 60,变量 B 的值为 13,则:
运算符 | 描述 | 实例 |
& | 对两个操作数的每一位执行逻辑与操作,如果两个相应的位都为 1,则结果为 1,否则为 0。 按位与操作,按二进制位进行"与"运算。 | (A & B) 将得到 12,即为 0000 1100 |
| | 对两个操作数的每一位执行逻辑或操作,如果两个相应的位都为 0,则结果为 0,否则为 1。 按位或运算符,按二进制位进行"或"运算。 | (A | B) 将得到 61,即为 0011 1101 |
^ | 对两个操作数的每一位执行逻辑异或操作,如果两个相应的位值相同,则结果为 0,否则为 1。 异或运算符,按二进制位进行"异或"运算。运算规则: | (A ^ B) 将得到 49,即为 0011 0001 |
~ | 对操作数的每一位执行逻辑取反操作,即将每一位的 0 变为 1,1 变为 0。 取反运算符,按二进制位进行"取反"运算。 | (~A ) 将得到 -61,即为 1100 0011,一个有符号二进制数的补码形式。 |
<< | 将操作数的所有位向左移动指定的位数。左移 n 位相当于乘以 2 的 n 次方。二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。 | A << 2 将得到 240,即为 1111 0000 |
>> | 将操作数的所有位向右移动指定的位数。右移n位相当于除以 2 的 n 次方。二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补 0,负数左补 1,右边丢弃。 | A >> 2 将得到 15,即为 0000 1111 |
2.4.5 赋值运算符
下表列出了 C 语言支持的赋值运算符:< 左值表达式 > = < 右值表达式 >、< 变量 > < 操作符 >= < 表达式 >
运算符 | 描述 | 实例 |
= | 简单的赋值运算符,把右边操作数的值赋给左边操作数 | C = A + B 将把 A + B 的值赋给 C |
+= | 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 | C += A 相当于 C = C + A |
-= | 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 | C -= A 相当于 C = C - A |
*= | 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 | C *= A 相当于 C = C * A |
/= | 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 | C /= A 相当于 C = C / A |
%= | 求模且赋值运算符,求两个操作数的模赋值给左边操作数 | C %= A 相当于 C = C % A |
<<= | 左移且赋值运算符 | C <<= 2 等同于 C = C << 2 |
>>= | 右移且赋值运算符 | C >>= 2 等同于 C = C >> 2 |
&= | 按位与且赋值运算符 | C &= 2 等同于 C = C & 2 |
^= | 按位异或且赋值运算符 | C ^= 2 等同于 C = C ^ 2 |
|= | 按位或且赋值运算符 | C |= 2 等同于 C = C | 2 |
2.4.6 特殊运算符
1)条件运算符 "? :"是三目运算符 , 其运算的一般形式是 :< 表达式 1> ? < 表达式 2> : < 表达式 3>
int x=82, y=101;
x >= y ? x+18 : y-100 // 运算结果为 1
x < (y-11) ? x-22 : y-1 // 运算结果为 60
2) 逗号运算符:从左往右分别求两个表达式的值,并以表达式2(即最右边的一个表达式)的值作为整个逗号表达式的值。
i =(a=2*3, a*5), a+6; //i最终结果为 30
/*
step1:a = 2*3; 结果a=6
step2:计算a*5; 结果是30,此时a的值是不变的,仍旧是6
step3:i=(a=2*3,a*5);得到i=30,因为赋值符号=的优先级比逗号的高,所以直接把30赋值给了i,不执行a+6。
*/
2.4.7 运算符优先级
下表将按运算符优先级从高到低列出各个运算符,具有较高优先级的运算符出现在表格的上面,具有较低优先级的运算符出现在表格的下面。在表达式中,较高优先级的运算符会优先被计算。
优先级 | 运算符 | 名称或含义 | 使用形式 | 结合方向 | 说明 |
1 | [] | 数组下标 | 数组名[常量表达式] | 左到右 | -- |
() | 圆括号 | (表达式)/函数名(形参表) | -- | ||
. | 成员选择(对象) | 对象.成员名 | -- | ||
-> | 成员选择(指针) | 对象指针->成员名 | -- | ||
2 | - | 负号运算符 | -表达式 | 右到左 | 单目运算符 |
~ | 按位取反运算符 | ~表达式 | |||
++ | 自增运算符 | ++变量名/变量名++ | |||
-- | 自减运算符 | --变量名/变量名-- | |||
* | 取值运算符 | *指针变量 | |||
& | 取地址运算符 | &变量名 | |||
! | 逻辑非运算符 | !表达式 | |||
(类型) | 强制类型转换 | (数据类型)表达式 | -- | ||
sizeof | 长度运算符 | sizeof(表达式) | -- | ||
3 | / | 除 | 表达式/表达式 | 左到右 | 双目运算符 |
* | 乘 | 表达式*表达式 | |||
% | 余数(取模) | 整型表达式%整型表达式 | |||
4 | + | 加 | 表达式+表达式 | 左到右 | 双目运算符 |
- | 减 | 表达式-表达式 | |||
5 | << | 左移 | 变量<<表达式 | 左到右 | 双目运算符 |
>> | 右移 | 变量>>表达式 | |||
6 | > | 大于 | 表达式>表达式 | 左到右 | 双目运算符 |
>= | 大于等于 | 表达式>=表达式 | |||
< | 小于 | 表达式<表达式 | |||
<= | 小于等于 | 表达式<=表达式 | |||
7 | == | 等于 | 表达式==表达式 | 左到右 | 双目运算符 |
!= | 不等于 | 表达式!= 表达式 | |||
8 | & | 按位与 | 表达式&表达式 | 左到右 | 双目运算符 |
9 | ^ | 按位异或 | 表达式^表达式 | 左到右 | 双目运算符 |
10 | | | 按位或 | 表达式|表达式 | 左到右 | 双目运算符 |
11 | && | 逻辑与 | 表达式&&表达式 | 左到右 | 双目运算符 |
12 | || | 逻辑或 | 表达式||表达式 | 左到右 | 双目运算符 |
13 | ?: | 条件运算符 | 表达式1? 表达式2: 表达式3 | 右到左 | 三目运算符 |
14 | = | 赋值运算符 | 变量=表达式 | 右到左 | -- |
/= | 除后赋值 | 变量/=表达式 | -- | ||
*= | 乘后赋值 | 变量*=表达式 | -- | ||
%= | 取模后赋值 | 变量%=表达式 | -- | ||
+= | 加后赋值 | 变量+=表达式 | -- | ||
-= | 减后赋值 | 变量-=表达式 | -- | ||
<<= | 左移后赋值 | 变量<<=表达式 | -- | ||
>>= | 右移后赋值 | 变量>>=表达式 | -- | ||
&= | 按位与后赋值 | 变量&=表达式 | -- | ||
^= | 按位异或后赋值 | 变量^=表达式 | -- | ||
|= | 按位或后赋值 | 变量|=表达式 | -- | ||
15 | , | 逗号运算符 | 表达式,表达式,… | 左到右 | -- |
2.5 关键字
C语言有32个关键字,关键字类型关键字列表:
-
- 基本数据类型: signed unsigned char int float double short long void
- 构造数据类型: struct union enum
- 数据存储类别: auto static extern register
- 数据优化: const volatile
- 四种结构: if else switch case break default while do for return continue goto
- 其它: typedef sizeof
2.5.1 基本数据类型
> 1、unsigned :无符号的 //用来声明一个无符号的变量。
unsigned char var; //var的范围:0~255
> 2、signed :有符号的(可以省略不写) //用来声明一个有符号的变量。
signed char var; //var的范围:-128~127
> 3、char :字符型 //用来声明一个字符型变量,占一个字节空间。
char var;
> 4、int :整型 //用来声明一个整型变量。C51:占两个字节空间,ARM:占四个字节
//大小由编译器来决定(32位或64位操作系统下为4字节)
int var;
> 5、float :浮点型
//用来声明一个浮点型(实型)变量。
//最多能表示到7个有效数据位。占四个字节空间
float var;
> 6、double :双精度型
//用来声明一个双精度实型变量。
//最多能表示到15~16个有效数据位。
//占八个字节
double var;
> 7、short :短整型
//用来声明一个短整型变量。
//C51:跟int一样,ARM:占两个字节
short var;
> 8、long :长整型
//用来声明一个长整型变量。
//ARM:跟int一样,C51:占四个字节
long var;
> 9、void :空型
//表示一个函数没有返回值,或者无形参。
void function(void);
2.5.2 构造数据类型
> 1、struct
//用来声明一种结构体类型。里面的变量有顺序要求,字节对齐;
struct stu{
char sex;
int age;
float score;
struct stu *Next;
};
struct stu var;
> 2、union
//用来声明一种共用体类型。
//该类型的变量所在空间的大小以其成员占最大的那个为准,
//存入该变量中的值以程序中最后存入的数值为当前值
union non{
char sex;
int age;
float score;
};
union non var;
> 3、enum
//用来声明一种枚举类型(整型常量集合)。
//规定枚举类型的变量,只能在限定的范围内取值
//否则,编译会出现警告(达到数据安全的效果)
enum em
{a = 23,b,c,d = 56,e}; //其中b=24,c=25,e=57
enum em var;
2.5.3 数据的存储类别
> 1、auto :自动的(可省略不写)
定义一个局部变量,默认为auto类型的,当它所在的函数调用结束时,释放内存
使用时才分配内存,用完即释放
auto char var;
> 2、static :静态的
//①定义一个局部变量,该变量在定义时只进行一次初始化,以后每次调用它所在的函数,其值
//都会保持上一次调用的结果,它所在的空间不会被释放;
//②被static修饰的全局变量,则只能在它所在的C源文件中使用,其它文件不能调用(内部全局变量)
//③被static修饰的函数,只能在该函数所在的C源文件中被调用,其它文件不能调用(内部函数)
static char var;
static void function();
> 3、extern :外部的
//①想要调用别的C源文件中的某一个全局变量,可以使用该关键字在该文件中修饰声明该变量
//即可调用(前提是该变量没有被static修饰),该类型的变量也是一直占着内存不释放
//②想要调用别的C源文件中的某一个函数,可以使用该关键字在该文件中修饰声明该函数
//即可调用(前提是该函数没有被static修饰)
extern char var;
extern void function();
> 4、register :寄存器的
//被这个关键字修饰的变量,建议编译器将该变量在
//使用时放到CPU内部寄存器中,以提高执行效率
//注意:该关键字只是"建议",到底有没有将变量放到
//寄存器中无从得知。一般使用在循环次数比较多的地方。
//在使用时才分配内存,用完即释放
register long i = 30000;
2.5.4 数据优化
> 1、const :常的
//常变量:被const关键字声明的变量,其值不能被改变。
//即在声明该变量时必须要初始化该变量。
//var本身还是一个变量。(数据安全检测)
const char var = 100;
char arr[var]; //试图声明一个长度为100的字符型数组
//在MDK (ARM)中可以使用常变量来声明数组的长度
//在VC++编译环境中也可以。
//在C51-keil中不可以使用常变量来声明数组的长度
char *const p;
//指针变量p不可改变,但是它指向的地址里面的值可变
char const *p; 或 const char *p;
//指针变量p可以改变,但是它所指向的地址里面的值不能改变
const char * const p; //p地址不可改变,里面的值也不能变
> 2、volatile :随时会改变的
//被volatile修饰的变量或寄存器会意想不到地发生改变。
//①某些变量或寄存器(如状态寄存器)可能会受硬件影响;
//②在多线程任务中,被其它线程修改(共享内存段);
//③一个在中断服务中使用到的全局变量
//④编译器会对C程序进行优化;
//为了保证程序不被优化,保证CPU取到的数据是最新的
//(即提醒CPU每次都必须到内存中取出变量当前的值而不
//是取cache或者寄存器中的备份),使用该关键字修饰,如:
int *p; *p = 1; *p = 2;
//编译时,编译器会对上面的语句进行优化,
//会直接优化成:
int *p; *p = 2;
//为了保证上面的语句不被优化,加volatile修饰变量:
int * volatile p;
2.5.5 四种结构
四种结构:
> 1、顺序结构:0条 //声明语句、运算语句、赋值语句等等
> 2、选择结构:2条 //多选一
①、if -else if -else if ... else
if(表达式1)
{语句s;}
else if(表达式2)
{语句s;}
else if(表达式3)
{语句s;}
else
{语句s;}
//用法:顺序判断if后的"表达式"是否为真
//如果碰到为真的情况,则执行其下面的{}里的"语句"
//执行完后,即退出这个"多选一"的结构
②、switch-case-break
switch(变量)
{
case 常量1:语句;...;break;
case 常量2:语句;...;break;
case 常量3:语句;...;break;
default:语句;
}
//用法:顺序判断"变量"是否与"常量"相等,
//如果相等,则执行该常量:后的"语句s",遇到break即跳出这个结构
unsigned char i = 6;
unsigned char dat;
switch(i)
{
case 3:dat = 5;break;
case 5:dat = 34;break;
case 6:dat = 99;break;
case 7:dat = 56;break;
case 6:dat = 100;break;
default:dat = 68; //默认执行
}
//注:如果少了break,则顺序判断i的值与case后面的常量是否相等,如果相等,则执行其
//后面的语句,以后不再判断,再继续执行下面的每一条case 后面的语句,直到default.
//这种用法不使用!
> 3、循环结构:3条
①、for
for(语句1;语句2;语句3)
{
语句4;
语句...;
}
//用法:语句1:条件初始化
// 语句2:判断语句,判断条件是否成立
// 语句3:修改条件语句
//先执行语句1,再进行对语句2的判断,如果成立
//则执行{}里的语句4...,再执行语句3,在判断
//语句2是否依然成立,。当语句2不成立时,结束循环
②、while
while(表达式)
{
语句;
....;
}
//用法:先判断“表达式”是否成立,如果成立
//则执行{}里的语句,执行完再次判断“表达式”
//是否依然成立,成立则继续执行{},不成立则结束
//此循环结构。
如何来设计一个死循环?两种方法:
for(;;)
while(1)
③、do-while
do{
语句1;
...;
}while(表达式);
//用法:先执行{}里的语句,执行完后,判断
//"表达式"是否成立,如果成立,继续执行{};
//如果不成立,则结束循环
> 4、转移结构:4条
①、break
//仅用于跳出循环结构
//且仅能跳出一层循环结构
for(i=10;i>0;i--)
{
t = 10;
while(t--)
{
dat++;
if(dat == 3)
break;//跳出while()结构。
}
}
②、continue
//用于终止本次循环,继续从下次循环开始,正式程序中不使用,仅用于调试程序
char buf = 10;
while(buf--)
{
a++;
b++;
continue;//遇到continue则结束这次循环
d++; //这条永远都不会执行到
e++; //这条永远都不会执行到
}
③、goto
//无条件转移,一般都不建议在大的程序当中使用
unsigned char dat=10;
while(dat--)
{
a++;
b++;
Lable: c++;
if(c == 3)
goto Lable;//直接跳到Lable标号去
d++;
}
④、return
//用于函数返回,
//在函数调用时,遇到return即返回。
//如果需要返回某个值,在其后加上返回值。
//返回值类型必须和函数类型一致。
void function()
{
char a,b;
a++;
return; //遇到return即返回调用处
b++; //不会被执行
return; //不会被执行
}
2.5.6 其他
> 1、typedef : 类型重定义
typedef unsigned char uchar;
//用uchar重新定义unsigned char
#define uchar unsigned char
//用uchar 替换unsigned char
跟宏定义的区别:
①、typedef 是在编译阶段进行定义,宏定义是在预编译处理阶段完成展开
②、typedef 是类型重新定义,不是简单地替换,宏定义只是简单的替换,没有定义
typedef unsigned char* M;
#define M unsigned char*
M p1,p2;
//对于typedef来说:p1是指针变量,p2也是
//对于宏定义来说:p1是指针变量,p2是普通变量
> 2、sizeof
//变量类型或变量占据多少内存空间,返回字节数
int var = 10;
char g;
g = sizeof(var++);//g = 4; 注意:var++该条语句没有被执行!