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

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位系统字节大小
char11
short22
int44
long48
long long88
float44
double88
long double8或128或16
指针(int*, char* 等)48
size_t48
ptrdiff_t48

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

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

例程如下:

#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关键字的主要用途是:

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

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

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两者在数据类型操作上的区别。

  • 25
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值