当前位置:   article > 正文

C语言学习(一)关键字

C语言学习(一)关键字

本学习参考书目《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关键字语句的区别:从条件范围限制上:if-else 可以处理范围更广的测试条件,包括逻辑表达式和复杂的条件判断;switch-case 只能用于整数、字符和枚举类型的表达式,不支持范围检查或复杂的条件表达式;从类型灵活性上:if-else 可以使用任何结果为布尔值的表达式,例如整数、字符、指针、浮点数或者布尔表达式;switch-case 的表达式必须是整型或枚举类型,不能是浮点型、结构体或类类型;从性能上来说:if-else 在表达式很多或者很复杂时,可能导致效率稍低,因为每个条件表达式都需要依次评估,直到找到第一个为真的条件;switch-case 在处理大量值时通常比 if-else 更高效,特别是当这些值集中在一个范围内时,编译器可以优化为跳转表,实现快速直接的条件跳转。

控制语句---循环关键字详解

  • 循环语句的工作方式:通过条件表达式的判定是否执行循环体;条件表达式遵循上述中if语句表达式的原则;
  • do、while、for:do语句先执行后判断,循环体至少执行一次;while语句先判断后执行,循环体可能不执行;for语句先判断后执行,相比while更简洁;
  • break和continue语句:break表示终止本层循环,continue表示终止本次循环,进入下一次循环;

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 * 转换为其他类型的指针;例子如下:

  1. void *ptr;
  2. int x = 10;
  3. ptr = &x; // ptr 现在指向一个 int

自定义memset函数,帮助理解void泛型指针功能:
 

  1. #include <stdio.h>
  2. void *myMemset(void *s, int c, size_t n) {
  3. unsigned char *p = s; // 将 void 指针转换为 unsigned char 指针
  4. while (n--) {
  5. *p++ = (unsigned char)c;
  6. }
  7. return s;
  8. }
  9. int main() {
  10. char array[10];
  11. printf("Array before memset:\n");
  12. for (int i = 0; i < 10; i++) {
  13. array[i] = 'a' + i;
  14. printf("%c ", array[i]);
  15. }
  16. myMemset(array, 'x', 10);
  17. printf("\nArray after memset:\n");
  18. for (int i = 0; i < 10; i++) {
  19. printf("%c ", array[i]);
  20. }
  21. return 0;
  22. }

 3、用作函数参数:这个用法是为了区分C语言和C++语言。在C中,不写参数的空括号 () 意味着函数可以接受任意类型的参数,而 (void) 明确说明函数不接受任何参数。在C++中,()(void) 都表示函数不接受任何参数,但在C中最好使用 (void);例子如下:

  1. void myFunction(void) {
  2. // 这个函数不接受任何参数
  3. // ...
  4. }
数据类型32位系统字节大小64位系统字节大小
char11
short22
int44
long48
long long88
float44
double88
long double8或128或16
指针(int*, char* 等)48
size_t48
ptrdiff_t48

数据类型关键字用于声明变量或函数的类型。数据类型是固定内存大小的别名,是创建变量的模子,数据类型决定了数据的表示形式,存储方式,可以对数据进行的操作类型以及数据操作的方法。

变量:变量是数据类型实例的具体化,它是程序中用于存储数据的内存位置的名称。变量是一段实际连续存储空间的别名;程序通过变量来申请并且命名存储空间;通过变量的名字可以使用存储空间。变量的创建需要数据类型的指定,开辟对应的存储空间。

例程如下:

  1. #include <stdio.h>
  2. typedef int INT32;
  3. typedef unsigned char BYTE;
  4. typedef struct _demo
  5. {
  6. short s;
  7. BYTE b1;
  8. BYTE b2;
  9. INT32 i;
  10. }DEMO;
  11. int main()
  12. {
  13. INT32 i32;
  14. BYTE byte;
  15. DEMO d;
  16. printf("%zu, %zu\n", sizeof(INT32), sizeof(i32));
  17. printf("%zu, %zu\n", sizeof(BYTE), sizeof(byte));
  18. printf("%zu, %zu\n", sizeof(DEMO), sizeof(d));
  19. return 0;
  20. }


复合类型关键字

struct, union, enum
  • struct: 定义一个结构体,将多个不同类型的数据项组合成一个单一的复合类型;
  • union: 定义一个联合体,多个成员共享同一块内存空间;
  • enum: 定义一个枚举类型,为整数值提供更具可读性的名称。

 struct详解:
        1、空结构体的大小是1字节;这个结论是根据C标准,结构体必须占用至少一字节的空间,及时没有包含任何数据成员。这是为了确保每个不同的结构体实例在内存中都有一个唯一的地址。
        2、柔性数组:在结构体中加入数组时,采用柔性数组的定义方法。柔性数组成员是定义在结构体中最后的特殊数组。柔性数组的数据与结构体中其他成员数据在内存中连续的,有利于访问;并且只需一次内存分配就能同时分配结构体和数据数组的空间。使用柔性数组的基本规则:1、柔性数组必须是结构体的最后一个成员;2、结构体中必须至少有一个其他成员;3、柔性数组成员在声明时不指定大小。
柔性数组例程:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. //柔性数组例程
  4. // 定义包含柔性数组的结构体
  5. struct FlexibleArrayStruct {
  6. int length;
  7. double data[]; // 柔性数组成员
  8. };
  9. int main() {
  10. int n = 5;
  11. // 计算所需总内存大小
  12. size_t size = sizeof(struct FlexibleArrayStruct) + sizeof(double) * n;
  13. // 分配内存
  14. struct FlexibleArrayStruct *fas = (struct FlexibleArrayStruct *)malloc(size);
  15. // 检查内存分配是否成功
  16. if (fas == NULL) {
  17. perror("Failed to allocate memory");
  18. return EXIT_FAILURE;
  19. }
  20. // 初始化数据
  21. fas->length = n;
  22. for (int i = 0; i < fas->length; i++) {
  23. fas->data[i] = i * 1.0; // 任意数据赋值
  24. }
  25. // 输出数据
  26. printf("Array elements: ");
  27. for (int i = 0; i < fas->length; i++) {
  28. printf("%.1f ", fas->data[i]);
  29. }
  30. printf("\n");
  31. // 释放内存
  32. free(fas);
  33. return 0;
  34. }

        3、struct和class的区别:在c++中struct和class一般可以通用,只有很小的区别。struct的成员一般默认是public的,而class的成员是private的。

union详解:在union中,所有的数据成员共用一个空间,同一时间只能储存其中的一个数据成员,所有的数据成员具有相同的起始地址。所以,联合体中最大的成员决定了联合体的总大小。
        下列是一个例程展示如何定义union,并且在union中如何存储和读取数据:

  1. #include <stdio.h>
  2. union Data {
  3. int i;
  4. float f;
  5. char str[20];
  6. };
  7. int main() {
  8. union Data data;
  9. data.i = 10;
  10. printf("data.i : %d\n", data.i);
  11. data.f = 220.5;
  12. printf("data.f : %f\n", data.f);
  13. printf("data.i : %d\n", data.i); // data.i 的值现在是未定义的
  14. sprintf(data.str, "C Programming");
  15. printf("data.str : %s\n", data.str);
  16. printf("data.f : %f\n", data.f); // data.f 的值现在是未定义的
  17. return 0;
  18. }

enum详解: enum是枚举类型,枚举类型是由一组命名的整型常量组成的列表;enum是一种自定义类型;enum默认常量在前一个值的基础上一次加1;enum类型的变量只能取定义是的离散值;
        enum例程:enum中默认下一个变量的值是前一个变量+1;但是在enum中也可以显式的为整形变量进行赋值,指定每一个常量的值;

  1. #include <stdio.h>
  2. enum Weekday {
  3. Sunday, // 默认为0
  4. Monday, // 默认为1
  5. Tuesday, // 默认为2
  6. Wednesday, // 默认为3
  7. Thursday, // 默认为4
  8. Friday, // 默认为5
  9. Saturday // 默认为6
  10. };
  11. int main() {
  12. enum Weekday today = Wednesday;
  13. printf("Today is day number %d of the week.\n", today);
  14. return 0;
  15. }

枚举类型和#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;
  1. int value = 10;
  2. int anotherValue = 20;
  3. const int *ptr = &value;
  4. printf("Value: %d\n", *ptr); // 正确,读取数据
  5. // *ptr = 15; // 错误,不能通过ptr修改value的值
  6. ptr = &anotherValue; // 正确,ptr可以指向另一个地址

        2、 常量指针:当const关键字位于星号 (*) 右侧时,它指定指针本身是常量,这意味着指针不能被修改,一旦指向某个地址后就不能指向其他地址,但是该指针指向的地址对应的数据可以修改。

int *const ptr;
  1. int value = 10;
  2. int anotherValue = 20;
  3. int *const ptr = &value;
  4. *ptr = 15; // 正确,可以通过ptr修改value的值
  5. // ptr = &anotherValue; // 错误,ptr是常量,不能指向另一个地址

        3、指向常量的常量指针: 当 const 两次出现,一次位于星号 (*) 的左侧,一次位于右侧时,这意味着指针既不能被修改指向其他地址,它指向的数据也不能被修改。

const int *const ptr;
  1. int value = 10;
  2. const int *const ptr = &value;
  3. // *ptr = 15; // 错误,不能通过ptr修改value的值
  4. // ptr = &anotherValue; // 错误,ptr是常量,不能指向另一个地址

const修饰指针的口诀: 左数右指    当const出现在*号左边时,指针指向的数据为常量(指向可变,数据不能变);当const出现在*号右边时,指针本身为常量(指向不可变,数据可变)。

        const修饰函数参数和返回值:const修饰函数传入参数,是不希望这个参数值呗函数体内以外改变时使用的。即在调用该函数时,不希望在函数的内部修改参数的值。const修饰函数的返回值时,表示函数返回值不可被改变,多用于返回指针的情形。下列两个例子说明:

  1. #include <stdio.h>
  2. //使用const修饰函数参数
  3. void printArray(const int* arr, int size) {
  4. for (int i = 0; i < size; i++) {
  5. printf("%d ", arr[i]);
  6. }
  7. printf("\n");
  8. // 下面的代码如果取消注释将会导致编译错误,因为arr是指向const的指针
  9. // arr[0] = 10;
  10. }
  11. int main() {
  12. int numbers[] = {1, 2, 3, 4, 5};
  13. printArray(numbers, 5);
  14. return 0;
  15. }
  1. #include <stdio.h>
  2. //使用const修饰函数的返回值,定义新的变量接收这个返回值时,必须也要是const修饰的类型
  3. const char* getErrorMessage(int error) {
  4. const char* messages[] = {"Success", "Error: Invalid input", "Error: Out of memory"};
  5. if (error < 0 || error >= 3) {
  6. return "Error: Unknown";
  7. }
  8. return messages[error];
  9. }
  10. int main() {
  11. const char* message = getErrorMessage(2);
  12. printf("%s\n", message);
  13. // 下面的代码如果取消注释将会导致编译错误,因为message是一个const char*
  14. // message[0] = 'X';
  15. return 0;
  16. }

volatile:volatile关键字的意思是易变的,不稳定的意思。volatile修饰的变量表示这个表里可以被某些编译器位置的因素更改,比如操作系统、硬件或者其他线程等。于是,编译器遇到volatile修饰的变量,就可以提供对特殊地址的稳定访问,避免出变量修改,而编译器不知道,引发错误。

volatile关键字的主要用途是:

  1. 与硬件的直接交互: 当编写与物理硬件(如传感器、寄存器等)交互的代码时,硬件的状态或数据可能会随时变化,而不受程序的控制。使用 volatile 确保每次都从硬件读取最新数据。

  2. 多线程编程中共享变量: 在多线程应用中,一个线程可能修改一个变量,而这个变量同时被另一个线程使用。标记为 volatile 的变量会防止编译器做出可能会忽略其他线程所作修改的优化。

  3. 中断服务例程: 在中断服务例程(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 操作符对其操作数不进行求值,只计算其数据类型的存储大小。这意味着即使操作数是一个函数调用或复杂表达式,这些表达式也不会真正执行;两种用法:

计算数据类型的大小:可以直接用于数据类型,以确定该类型的实例占用的字节大小。

  1. size_t intSize = sizeof(int);
  2. size_t doubleSize = sizeof(double);
  3. size_t charSize = sizeof(char);

计算变量的大小:可以用于变量,包括基本类型变量、数组、结构体等,来获取变量实际占用的内存大小。

  1. int x = 10;
  2. double y[10];
  3. size_t xSize = sizeof(x);
  4. size_t ySize = sizeof(y);

 typedef关键字详解:
        typedef关键字的作用是为数据类型创建新的名称,也就是给目前已有的数据类型起别名,并没有创造新的数据类型。例如下面这个例子:在代码中,uint成为了unsigned int的别名。

  1. typedef unsigned int uint;
  2. uint x = 100;

 typedef的优点是:通过为数据类型定义更具描述性的名称,可以使代码更加清晰和易于理解。

注:区别typedef和#define两者在数据类型操作上的区别。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/587708
推荐阅读
相关标签
  

闽ICP备14008679号