赞
踩
本学习参考书目《c语言深度剖析》,视频教程--国嵌唐老师(c语言深入剖析班)
什么是c语言下的关键字?
C语言的关键字是那些被语言赋予了特殊意义的词汇。这些词汇用作C程序的基础结构,用于声明数据类型、控制结构、指定存储类别等等。关键字不能用作变量名、函数名或其他标识符名称,因为它们是为语言的编程结构保留的。
下列可以将c语言中关键字分类为:
控制语句关键字:
if, else, switch, case, default, while, do, for, goto, continue, break, return
if
, else
: 用于基于条件表达式的执行路径选择;switch、
case、
default
: switch
语句根据表达式的值选择多个代码块之一执行。case
标记每个选择,default
为默认选项;while
: 根据条件表达式的真假重复执行一个语句块;do
...while
: 至少执行一次语句块,然后根据条件表达式决定是否继续执行;for
: 一个控制循环迭代的语句,可以初始化变量,设置循环继续的条件,以及更新循环控制变量;goto
: 直接跳转到程序的另一部分;禁用:可读性差,维护困难;continue
: 跳过当前循环的剩余部分,立即开始下一次迭代;break
: 退出最内层的循环或switch
语句;return
: 从函数返回一个值(如果有)并结束该函数。控制语句---选择关键字详解:
if
, else:
在C语言中,任何非零值都被视为真(true
),零值视为假(false
);不要在 if
语句后直接跟分号,这会导致 if
语句没有执行体;避免直接比较浮点数是否相等,由于浮点数的精度问题,这种比较可能不准确。总结:在判断语句的判断条件中:bool型变量应该直接出现在条件中,不要进行比较;普通变量和0值进行比较时,0值应该出现在比较符号左边;float型变量不能直接进行0值比较,需要定义精度。switch:
种switch语句对应单个条件多个分支的情形,每个case语句分支必须要有break,否则会导致分支重叠;default语句有必要加上,以处理特殊情况。if-else
可以处理范围更广的测试条件,包括逻辑表达式和复杂的条件判断;switch-case
只能用于整数、字符和枚举类型的表达式,不支持范围检查或复杂的条件表达式;从类型灵活性上:if-else
可以使用任何结果为布尔值的表达式,例如整数、字符、指针、浮点数或者布尔表达式;switch-case
的表达式必须是整型或枚举类型,不能是浮点型、结构体或类类型;从性能上来说:if-else
在表达式很多或者很复杂时,可能导致效率稍低,因为每个条件表达式都需要依次评估,直到找到第一个为真的条件;switch-case
在处理大量值时通常比 if-else
更高效,特别是当这些值集中在一个范围内时,编译器可以优化为跳转表,实现快速直接的条件跳转。控制语句---循环关键字详解:
return详解:return关键字是用于从函数中返回一个值到调用者,并可以选择性的终止函数的执行。细节:当函数执行到return语句时,当前函数的执行被终止,控制权被转移回函数的调用者;如果 return出现在主函数 main()
中,它将结束主程序的执行,并将返回值传递给操作系统;
数据类型关键字:
int, short, long, float, double, char, unsigned, signed, void
int
, short
, long
: 用于声明整数类型的变量,具有不同的存储大小;float
, double
: 用于声明单精度和双精度浮点数;char
: 用于声明字符类型的变量;unsigned
, signed
: 指定整数类型为无符号或有符号;void
: 指示没有值。void关键字详解:主要有以下作用:
1、用于指定无返回值的函数或无类型的指针:用在函数返回类型的位置时,void
表明该函数不返回任何值;
2、定义泛型指针:void
类型的指针可以指向任何类型的数据(不管是变量还是数组的地址都可以用该指针去指向),这是一种泛型指针;void *
指针可以转换为其他类型的指针而无需显式类型转换。反之,将其他类型的指针转换为 void *
指针也是如此。但是,使用 void *
指针时不能直接进行指针运算,因为 void
类型的大小是未定义的,所以在进行指针运算之前必须将 void *
转换为其他类型的指针;例子如下:
- void *ptr;
- int x = 10;
- ptr = &x; // ptr 现在指向一个 int
自定义memset函数,帮助理解void泛型指针功能:
- #include <stdio.h>
-
- void *myMemset(void *s, int c, size_t n) {
- unsigned char *p = s; // 将 void 指针转换为 unsigned char 指针
- while (n--) {
- *p++ = (unsigned char)c;
- }
- return s;
- }
-
- int main() {
- char array[10];
-
- printf("Array before memset:\n");
- for (int i = 0; i < 10; i++) {
- array[i] = 'a' + i;
- printf("%c ", array[i]);
- }
-
- myMemset(array, 'x', 10);
-
- printf("\nArray after memset:\n");
- for (int i = 0; i < 10; i++) {
- printf("%c ", array[i]);
- }
-
- return 0;
- }
3、用作函数参数:这个用法是为了区分C语言和C++语言。在C中,不写参数的空括号 ()
意味着函数可以接受任意类型的参数,而 (void)
明确说明函数不接受任何参数。在C++中,()
和 (void)
都表示函数不接受任何参数,但在C中最好使用 (void);例子如下:
- void myFunction(void) {
- // 这个函数不接受任何参数
- // ...
- }
数据类型 | 32位系统字节大小 | 64位系统字节大小 |
---|---|---|
char | 1 | 1 |
short | 2 | 2 |
int | 4 | 4 |
long | 4 | 8 |
long long | 8 | 8 |
float | 4 | 4 |
double | 8 | 8 |
long double | 8或12 | 8或16 |
指针(int* , char* 等) | 4 | 8 |
size_t | 4 | 8 |
ptrdiff_t | 4 | 8 |
数据类型关键字用于声明变量或函数的类型。数据类型是固定内存大小的别名,是创建变量的模子,数据类型决定了数据的表示形式,存储方式,可以对数据进行的操作类型以及数据操作的方法。
变量:变量是数据类型实例的具体化,它是程序中用于存储数据的内存位置的名称。变量是一段实际连续存储空间的别名;程序通过变量来申请并且命名存储空间;通过变量的名字可以使用存储空间。变量的创建需要数据类型的指定,开辟对应的存储空间。
例程如下:
- #include <stdio.h>
-
- typedef int INT32;
- typedef unsigned char BYTE;
- typedef struct _demo
- {
- short s;
- BYTE b1;
- BYTE b2;
- INT32 i;
- }DEMO;
-
- int main()
- {
- INT32 i32;
- BYTE byte;
- DEMO d;
-
- printf("%zu, %zu\n", sizeof(INT32), sizeof(i32));
- printf("%zu, %zu\n", sizeof(BYTE), sizeof(byte));
- printf("%zu, %zu\n", sizeof(DEMO), sizeof(d));
-
- return 0;
- }
复合类型关键字:
struct, union, enum
struct
: 定义一个结构体,将多个不同类型的数据项组合成一个单一的复合类型;union
: 定义一个联合体,多个成员共享同一块内存空间;enum
: 定义一个枚举类型,为整数值提供更具可读性的名称。 struct详解:
1、空结构体的大小是1字节;这个结论是根据C标准,结构体必须占用至少一字节的空间,及时没有包含任何数据成员。这是为了确保每个不同的结构体实例在内存中都有一个唯一的地址。
2、柔性数组:在结构体中加入数组时,采用柔性数组的定义方法。柔性数组成员是定义在结构体中最后的特殊数组。柔性数组的数据与结构体中其他成员数据在内存中连续的,有利于访问;并且只需一次内存分配就能同时分配结构体和数据数组的空间。使用柔性数组的基本规则:1、柔性数组必须是结构体的最后一个成员;2、结构体中必须至少有一个其他成员;3、柔性数组成员在声明时不指定大小。
柔性数组例程:
- #include <stdio.h>
- #include <stdlib.h>
- //柔性数组例程
-
- // 定义包含柔性数组的结构体
- struct FlexibleArrayStruct {
- int length;
- double data[]; // 柔性数组成员
- };
-
- int main() {
- int n = 5;
- // 计算所需总内存大小
- size_t size = sizeof(struct FlexibleArrayStruct) + sizeof(double) * n;
- // 分配内存
- struct FlexibleArrayStruct *fas = (struct FlexibleArrayStruct *)malloc(size);
-
- // 检查内存分配是否成功
- if (fas == NULL) {
- perror("Failed to allocate memory");
- return EXIT_FAILURE;
- }
-
- // 初始化数据
- fas->length = n;
- for (int i = 0; i < fas->length; i++) {
- fas->data[i] = i * 1.0; // 任意数据赋值
- }
-
- // 输出数据
- printf("Array elements: ");
- for (int i = 0; i < fas->length; i++) {
- printf("%.1f ", fas->data[i]);
- }
- printf("\n");
-
- // 释放内存
- free(fas);
- return 0;
- }
3、struct和class的区别:在c++中struct和class一般可以通用,只有很小的区别。struct的成员一般默认是public的,而class的成员是private的。
union详解:在union中,所有的数据成员共用一个空间,同一时间只能储存其中的一个数据成员,所有的数据成员具有相同的起始地址。所以,联合体中最大的成员决定了联合体的总大小。
下列是一个例程展示如何定义union,并且在union中如何存储和读取数据:
- #include <stdio.h>
-
- union Data {
- int i;
- float f;
- char str[20];
- };
-
- int main() {
- union Data data;
-
- data.i = 10;
- printf("data.i : %d\n", data.i);
-
- data.f = 220.5;
- printf("data.f : %f\n", data.f);
- printf("data.i : %d\n", data.i); // data.i 的值现在是未定义的
-
- sprintf(data.str, "C Programming");
- printf("data.str : %s\n", data.str);
- printf("data.f : %f\n", data.f); // data.f 的值现在是未定义的
-
- return 0;
- }
enum详解: enum是枚举类型,枚举类型是由一组命名的整型常量组成的列表;enum是一种自定义类型;enum默认常量在前一个值的基础上一次加1;enum类型的变量只能取定义是的离散值;
enum例程:enum中默认下一个变量的值是前一个变量+1;但是在enum中也可以显式的为整形变量进行赋值,指定每一个常量的值;
- #include <stdio.h>
-
- enum Weekday {
- Sunday, // 默认为0
- Monday, // 默认为1
- Tuesday, // 默认为2
- Wednesday, // 默认为3
- Thursday, // 默认为4
- Friday, // 默认为5
- Saturday // 默认为6
- };
-
- int main() {
- enum Weekday today = Wednesday;
- printf("Today is day number %d of the week.\n", today);
- return 0;
- }
枚举类型和#define的区别:
1、使用#define宏常量时,只是简单的进行值替换,枚举常量是真正意义上的常量;
2、#define宏常量无法被调试,枚举常量可以;
3、#define宏常量五类型信息,枚举常量是一种特定类型的常量;
注:对宏定义#define的理解:宏定义是预处理器命令,用在编译之前替换文本;编译器只看到替换后的结果。
类型修饰符关键字:
const, volatile
const
: 指定变量的值不能被修改,常量;volatile
: 告诉编译器对象可以被某些编译器未知的因素更改,防止编译器对代码进行过度优化。const详解:在c语言中const修饰的变量是只读的,其本质还是变量;const修饰的变量会在内存占用空间;本质上const只对编译器有用,在运行时无用。在c语言中,const修饰数组时,数组也是只读,数组大小不可变。
const修饰指针:在C语言中,使用 const
修饰指针时,其放置的位置相对于指针符号 (*
) 非常关键,因为它决定了是指针指向的内容不能被改变,还是指针本身不能被改变,或两者都不能改变。下列例子说明指针常量和常量指针:
1、指向常量的指针:他指定指针指向的数据是常量,指针指向可以变,但是不能通过这个指针来修改数据(即可以读取这个指针指向地址对应的数据,但是不可以通过该指针来改变对应地址的数据值)。
const int *ptr;
- int value = 10;
- int anotherValue = 20;
- const int *ptr = &value;
-
- printf("Value: %d\n", *ptr); // 正确,读取数据
- // *ptr = 15; // 错误,不能通过ptr修改value的值
-
- ptr = &anotherValue; // 正确,ptr可以指向另一个地址
2、 常量指针:当const关键字位于星号 (*
) 右侧时,它指定指针本身是常量,这意味着指针不能被修改,一旦指向某个地址后就不能指向其他地址,但是该指针指向的地址对应的数据可以修改。
int *const ptr;
- int value = 10;
- int anotherValue = 20;
- int *const ptr = &value;
- *ptr = 15; // 正确,可以通过ptr修改value的值
- // ptr = &anotherValue; // 错误,ptr是常量,不能指向另一个地址
3、指向常量的常量指针: 当 const
两次出现,一次位于星号 (*
) 的左侧,一次位于右侧时,这意味着指针既不能被修改指向其他地址,它指向的数据也不能被修改。
const int *const ptr;
- int value = 10;
- const int *const ptr = &value;
-
- // *ptr = 15; // 错误,不能通过ptr修改value的值
- // ptr = &anotherValue; // 错误,ptr是常量,不能指向另一个地址
const修饰指针的口诀: 左数右指 当const出现在*号左边时,指针指向的数据为常量(指向可变,数据不能变);当const出现在*号右边时,指针本身为常量(指向不可变,数据可变)。
const修饰函数参数和返回值:const修饰函数传入参数,是不希望这个参数值呗函数体内以外改变时使用的。即在调用该函数时,不希望在函数的内部修改参数的值。const修饰函数的返回值时,表示函数返回值不可被改变,多用于返回指针的情形。下列两个例子说明:
- #include <stdio.h>
- //使用const修饰函数参数
- void printArray(const int* arr, int size) {
- for (int i = 0; i < size; i++) {
- printf("%d ", arr[i]);
- }
- printf("\n");
-
- // 下面的代码如果取消注释将会导致编译错误,因为arr是指向const的指针
- // arr[0] = 10;
- }
-
- int main() {
- int numbers[] = {1, 2, 3, 4, 5};
- printArray(numbers, 5);
- return 0;
- }
- #include <stdio.h>
- //使用const修饰函数的返回值,定义新的变量接收这个返回值时,必须也要是const修饰的类型
- const char* getErrorMessage(int error) {
- const char* messages[] = {"Success", "Error: Invalid input", "Error: Out of memory"};
- if (error < 0 || error >= 3) {
- return "Error: Unknown";
- }
- return messages[error];
- }
-
- int main() {
- const char* message = getErrorMessage(2);
- printf("%s\n", message);
-
- // 下面的代码如果取消注释将会导致编译错误,因为message是一个const char*
- // message[0] = 'X';
-
- return 0;
- }
volatile:volatile关键字的意思是易变的,不稳定的意思。volatile修饰的变量表示这个表里可以被某些编译器位置的因素更改,比如操作系统、硬件或者其他线程等。于是,编译器遇到volatile修饰的变量,就可以提供对特殊地址的稳定访问,避免出变量修改,而编译器不知道,引发错误。
volatile关键字的主要用途是:
与硬件的直接交互: 当编写与物理硬件(如传感器、寄存器等)交互的代码时,硬件的状态或数据可能会随时变化,而不受程序的控制。使用 volatile
确保每次都从硬件读取最新数据。
多线程编程中共享变量: 在多线程应用中,一个线程可能修改一个变量,而这个变量同时被另一个线程使用。标记为 volatile
的变量会防止编译器做出可能会忽略其他线程所作修改的优化。
中断服务例程: 在中断服务例程(ISR)中,某些变量可能在中断中被改变,并在主程序中被查询。这些变量需要被声明为 volatile
,以保证主程序总是获取到最新的值。
存储类别关键字:
extern, static, auto, register
extern
: 声明一个全局变量(外部变量)或函数,其定义可能在另一个文件中;static
: 限制变量或函数的作用域,使之只在定义它的文件内可见;auto
: (几乎不用)默认存储类别,表示自动存储期的局部变量;register
: 建议编译器尝试将变量存储在寄存器中以快速访问。存储类别关键字详解:
extern:
别的文件中的变量或者函数要在本文件中使用的话,需要使用 extern
关键字来声明它,但不初始化;
auto
: c语言中局部变量的默认属性。auto属性的变量一般存储在栈上,auto
关键字声明的变量在其定义的代码块(通常是函数内部)执行时被创建,在退出代码块时被自动销毁。在C++中,auto
的用途已经被重新定义,用于类型推导,这是一个与C完全不同的概念和用法。
static
: 这一关键字可以影响变量的存储持续性、作用域和链接属性;该变量在代码文件中只被初始化一次;存储持续性:通常局部变量在函数调用完成后生命周期就结束了,存储在栈上。然而,使用 static
修饰的局部变量会持续存在直到程序结束,不管它是在函数内部定义的。static修饰的局部变量是存储在程序静态区的,这意味着它们的值在函数调用之间是保持的;限制作用域:通常全局变量对整个程序可见。但如果一个全局变量被声明为 static
,它的作用域被限制在定义它的文件内,其他文件不能链接到这个变量(即使使用 extern
关键字)。引出新的名词:文件作用域
register
: 请求将变量存储在寄存器中,但是只是请求寄存器变量,但是不一定成功,因为这只是一个建议并非强求,而且register变量必须是CPU寄存器可以接受的值。限制:地址操作限制:当变量被声明为 register
类型时,不能对该变量取地址。尝试使用 &
操作符获取 register
变量的地址会导致编译错误;优化的局限性:由于现代编译器具备自动优化能力,register
关键字在现代代码中的实际效果可能非常有限。
其他关键字:
sizeof
: 运算符,返回类型或变量的大小(以字节为单位)。typedef
: 为类型定义一个新名称;自定义数据类型。inline
: 建议编译器将函数体内联到每个调用点。_Bool
: 引入了布尔类型,表示真或假。_Complex
, _Imaginary
: 用于定义复数和虚数类型。sizeof关键字详解:
sizeof并不是一个内置函数,而是c语言中的内置关键字;用于计算变量或类型所占用的内存大小(以字节为单位);特点:编译时计算:sizeof
操作通常在编译时计算,而非运行时。这意味着 sizeof
产生的值在编译时就已确定,不会因为程序运行时的任何因素改变;返回类型:sizeof
返回的类型是 size_t
,这是一个无符号整型,足以表示内存中对象的大小;不求值表达式:sizeof
操作符对其操作数不进行求值,只计算其数据类型的存储大小。这意味着即使操作数是一个函数调用或复杂表达式,这些表达式也不会真正执行;两种用法:
计算数据类型的大小:可以直接用于数据类型,以确定该类型的实例占用的字节大小。
- size_t intSize = sizeof(int);
- size_t doubleSize = sizeof(double);
- size_t charSize = sizeof(char);
计算变量的大小:可以用于变量,包括基本类型变量、数组、结构体等,来获取变量实际占用的内存大小。
- int x = 10;
- double y[10];
- size_t xSize = sizeof(x);
- size_t ySize = sizeof(y);
typedef关键字详解:
typedef关键字的作用是为数据类型创建新的名称,也就是给目前已有的数据类型起别名,并没有创造新的数据类型。例如下面这个例子:在代码中,uint成为了unsigned int的别名。
- typedef unsigned int uint;
- uint x = 100;
typedef的优点是:通过为数据类型定义更具描述性的名称,可以使代码更加清晰和易于理解。
注:区别typedef和#define两者在数据类型操作上的区别。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。