C文档笔记

文档注释

/**为文档注释

/**
 *函数功能的说明
 *对参数的描述
 *返回什么
 */

32位机器和64位机器上C语言数据类型大小

数据类型32位64位
bool11
char11
unsigned char11
short int22
int44
指针48
unsigned int44
float44
long48
unsigned long48
double88
long long88
二者有区别的是指针longunsigned long;

输出乱码问题

ctrl + shift + alt + /不勾注册表中的run.processes.with.pty
原理:

run.processes.with.pty 选项影响的是 CLion 如何与运行的进程进行交互,特别是涉及到终端输入输出的情况。当你选择启用这个选项时,CLion 将使用伪终端(PTY)来运行和管理这些进程。在某些情况下,这可以改善对终端控制字符、编码和其他特定于终端的行为的支持。
如果不选中 run.processes.with.pty 选项而输出出现乱码,可能是因为没有 PTY 的情况下,CLion 直接处理输出流,而不经过一个完整的终端仿真层。这意味着一些依赖于终端特性(例如 ANSI 转义序列用于颜色或光标移动)的输出可能不会被正确解释,从而导致乱码现象。
当启用了 run.processes.with.pty,CLion 会通过伪终端来传递输出,这样它可以更准确地模拟真实的终端环境。这对于正确显示那些包含有非ASCII字符或者使用了特殊终端控制序列的应用程序输出非常重要。因此,在这种环境下,程序的输出应该能够正确地显示,包括正确的字符编码和格式化。

不定义第三个变量交换两个变量的值

最快的方式:

a = a ^ b;
b = a ^ b;
a = a ^ b;

还可以:

a = a + b;
b = a - b;
a = a - b;

常量指针和指针常量

常量指针

int x = 10;
const int *p = &x; //p是常量指针,指向的值不可变,地址可变;

示例:

#include <stdio.h>

int main() {
    int x = 10;
    const int *p = &x;  // p 是常量指针,指向的内容不可变
    //等价于 int const *p = &x;
    printf("x = %d\n", *p);  // 正常打印内容

    // *p = 20;  // 错误!通过 p 修改 x 的值是不允许的

    x = 20;  // 但可以直接修改 x 的值
    printf("x = %d\n", *p);  // 输出更新后的值

    return 0;
}

指针常量:

int x = 10;
int *const p = &x; //p是指针常量,地址不可变,指向的值可变

示例:

#include <stdio.h>

int main() {
    int x = 10;
    int y = 20;
    int * const p = &x;  // p 是指针常量,指向的位置不可变

    printf("x = %d\n", *p);  // 正常打印内容

    *p = 30;  // 通过 p 修改 x 的值是允许的
    printf("x = %d\n", *p);

    // p = &y;  // 错误!不能修改 p 指向的位置

    return 0;
}

结合

const int *const p = &x; //在这种情况下,p的指向和指向的内容都不能被修改;

函数指针和指针函数

函数指针

函数指针是指向函数的指针。它保存的是函数的地址,允许通过指针来调用函数;

返回类型(*函数名)(参数);

示例:

#include <stdio.h>

// 一个普通的函数,返回两个整数的和
int add(int a, int b) {
    return a + b;
}

int main() {
    // 定义一个指向函数的指针
    int (*func_ptr)(int, int); //把add替换成了(*func_ptr)

    // 将函数的地址赋给指针
    func_ptr = add;

    // 通过函数指针调用函数
    int result = func_ptr(2, 3);
    printf("Result: %d\n", result);  // 输出: Result: 5

    return 0;
}

指针函数

指针函数是返回指针的函数。也就是说,指针函数的返回类型是一个指针;

返回类型* 函数名(参数);

示例:

#include <stdio.h>

// 返回一个指向整数的指针
int* find_max(int* a, int* b) {
    if (*a > *b) {
        return a;
    } else {
        return b;
    }
}

int main() {
    int x = 10, y = 20;
    int* max = find_max(&x, &y);

    printf("Max value: %d\n", *max);  // 输出: Max value: 20

    return 0;
}

结构体对齐问题

内部对齐和总体对齐
公式:
内部对齐:

  • x=Min(默认大小,当前数据类型大小)
  • 当前数据的偏移地址(起始地址是)x的整数倍;
  • 32位机器默认大小是4,64位机器默认大小是8;
    总体对齐:
  • 32位机器一个结构体大小是4的整数倍,64位机器一个结构体大小是8的整数倍;

比如说:32位机器一个结构体内部算出来的大小是17,但是要满足总体对齐,最终大小是4的整数倍即20(要大于17),64位机器同理,在这里是总体大小是24;

示例:

#include <stdio.h>
struct MyStruct {
    int a;//4
    double b;//8
    char c;//1
    //32位机器算出来内部大小是13
    //64位机器算出来内部大小是17
};

int main() { 
	printf("内部大小: %zu\n", offsetof(struct MyStruct, c)+sizeof(char)); 
    printf("总体大小: %zu\n", sizeof(struct MyStruct)); return 0; 
}
//32位机器算出来总体大小是16
//64位机器算出来内部大小是24

Linux下32位和64位运行结果如下:
![[Pasted image 20241230214106.png]]

解释offsetof

  • offsetof(struct MyStruct, c) 计算的是成员 c 相对于结构体起始位置的偏移量
  • sizeof(char) 是 1,表示 char 类型的大小
  • 两者相加得到的是结构体中最后一个成员占用到的最后一个字节的位置,这样算出来的就是内部对齐后的大小;

函数指针

int max(int x,int y){
	return x>y?x:y;
}

int main(){
	int (*p)(int,int) = &max; //&可以省略
	int res = p(10,20);//等价于max
}

p是一个指向max的指针,不能写成int *p = &max,因为这样的话p是一个指向整形的指针,而max是一个函数,类型是int(int ,int),因此要 int (*p)(int ,int)=max;

回调函数

回调函数就是把函数指针传给另一个函数;
比如说:你请朋友帮你做一件事,并告诉他:“做完后给我打个电话。” 这里的“打电话”就是回调函数,而你的朋友是调用回调函数的“主函数”;

#include <stdio.h>

// 回调函数
void callback() {
    printf("Callback function is called!\n");
}

// 主函数,接受一个函数指针作为参数
void doSomething(void (*callback)()) {
    printf("Doing something...\n");
    callback(); // 调用回调函数
}

int main() {
    // 将 callback 函数作为参数传递给 doSomething
    doSomething(callback);
    return 0;
}

C语言的泛型

  1. 函数中的void*可以接受任意指针类型,用于泛型转换;
  2. typedef用来定义一个类型,下面的例子中就是用typedef来定义一个出传入两个void*参数,返回一个整形的Compare类型;
  3. 函数的声明只写参数类型,不写参数名;

普通函数

int foo(void*,void*);
int bar(void*,void*);

替换

typedef int(Compare)(void*,void*);

此时Compare就等价于foo,bar这类传入两个任意指针,返回整形的函数;

例子:

typedef int(Compare)(void*,void*);

//比较int类型
Compare compareInt(void* a, void* b){
	int* a = (int*)a;
	int* b = (int*)b;
	return *a - *b;
}

//比较char类型
Compare compareString(void* a, void* b) {  
	char* str1 = *(char**)a; 
	char* str2 = *(char**)b; 
	return strcmp(str1, str2); 
}

//排序中调用compare
void sort(void* array,int size,Compare* compare){
...
}

int main(){
	int arr[] = {3, 1, 4, 1, 5}; 
	sort(arr, 5, compareInt); // 使用整数比较函数排序 
	char* strs[] = {"apple", "banana", "cherry"}; 
    sort(strs, 3, compareString); // 使用字符串比较函数排序
}

C风格字符串

  • 字符串实际上是使用空字符 \0 结尾的一维字符数组。因此,\0 是用于标记字符串的结束;

相关函数:

序号函数 & 作用
1strcpy(s1, s2);复制字符串 s2 到字符串 s1;
2strcat(s1, s2);连接字符串 s2 到字符串 s1 的末尾;
3strlen(s1);返回字符串 s1 的长度;
4strcmp(s1, s2);如果 s1 和 s2 是相同的,则返回 0;如果 s1s2 则返回大于 0;
5strchr(s1, ch);返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置;
6strstr(s1, s2);返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置;

浅拷贝&深拷贝&转移表

浅拷贝

可以理解成多了一个别名,共享一个空间

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char* name;
    int age;
} Person;

int main() {
	Person p1 = ...;
	p1.name=...;

	Person p2=p1;
	//此时p2和p1都是指向同一个空间(同一个人),改了p2.name也就是改了p1.name

    return 0;
}

深拷贝

创建一个新的空间,编写函数“手动”将里面的内容复制到新的空间上;

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char* name;
    int age;
} Person;

// 深拷贝函数
void deepCopyPerson(Person* dest, const Person* src) {
    dest->age = src->age;
    dest->name = (char*)malloc(strlen(src->name) + 1); // 分配新的内存
    strcpy(dest->name, src->name); // 复制字符串内容
}

int main() {
    Person p1;
    Person p2;
    //...
    deepCopyPerson(p1,p2);//将p2的内容复制到p1
    
    return 0;
}

转移表

转移表是一种将数据从一个数据结构迁移到另一个数据结构的机制。它通常用于扩展数据容量或重新组织数据,复制的是内存内容,某些情况下像浅拷贝,某些情况下像深拷贝

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char name[50];
    int age;
} Person;

int main() {
    // 原始表
    Person originalTable[3] = {
        {"Alice", 30},
        {"Bob", 25},
        {"Charlie", 35}
    };

    // 计算原始表的大小
    int tableSize = sizeof(originalTable) / sizeof(originalTable[0]);

    // 创建一个新的表,大小比原来的表多1
    Person* newTable = (Person*)malloc((tableSize + 1) * sizeof(Person));
    if (newTable == NULL) {
        printf("Error: unable to allocate memory for new table.\n");
        return 1;
    }

    // 将数据从原始表迁移到新表
    memcpy(newTable, originalTable, tableSize * sizeof(Person));

    // 添加一个新人员到新表
    strcpy(newTable[tableSize].name, "David");
    newTable[tableSize].age = 22;

    // 输出新表的内容
    for (int i = 0; i < tableSize + 1; i++) {
        printf("Name: %s, Age: %d\n", newTable[i].name, newTable[i].age);
    }

    // 释放动态分配的内存
    free(newTable);

    return 0;
}
  • originalTable 是原始数据表,包含3个人员的信息。
  • newTable 是新表,大小比 originalTable 多1,用于扩展数据容量。
  • 使用 memcpy 将 originalTable 的数据迁移到 newTable 中。
  • 在 newTable 中添加一个新人员(“David”),并输出新表的内容。
  • 最后释放 newTable 的内存。

memcpy函数:第一个参数是目标地址,第二个参数是源地址,第三个参数是要复制的字节数,也就是以源地址为起始地址,往后复制多少字节的东西;

转移表复制是按字节复制内存的内容,某些情况下像浅拷贝,某些情况下像深拷贝,如下:

情况1

#include <stdio.h>
#include <string.h>

int main() {
    int src[] = {1, 2, 3, 4, 5};
    int dest[5];

    // 使用 memcpy 复制数据
    memcpy(dest, src, sizeof(src));

    // 修改 src 中的数据
    src[0] = 99;

    // 输出 dest 的内容
    for (int i = 0; i < 5; i++) {
        printf("%d ", dest[i]); // 输出:1 2 3 4 5
    }

    return 0;
}
  • memcpy 复制了 src 数组的所有内容到 dest 数组。
  • 修改 src 不会影响 dest,因为 memcpy 复制的是独立的数据。
  • 这种情况下,memcpy 的效果类似于深拷贝。

情况2

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char* name;
    int age;
} Person;

int main() {
    Person p1;
    p1.name = (char*)malloc(50);
    strcpy(p1.name, "Alice");
    p1.age = 30;

    Person p2;
    // 使用 memcpy 复制结构体
    memcpy(&p2, &p1, sizeof(Person));

    // 修改 p1 的 name
    strcpy(p1.name, "Bob");

    // 输出 p2 的 name
    printf("p2.name: %s\n", p2.name); // 输出:Bob

    // 释放内存
    free(p1.name);
    // 注意:p2.name 指向的内存已经被释放,不能再使用

    return 0;
}
  • memcpy 复制了 p1 到 p2,但只复制了 name 指针的值(地址),而不是 name 指向的字符串内容。
  • 修改 p1.name 会同时影响 p2.name,因为它们指向同一块内存。
  • 这种情况下,memcpy 的效果是浅拷贝。

命令参数

  • argc:表示命令行参数的数量:

    • 它是一个整数(int),表示用户在运行程序时输入了多少个参数;
    • 至少为 1,因为第一个参数总是程序的名称;
  • argv:表示命令行参数的值:

    • 它是一个字符串数组(char *argv[]),存储了每个参数的具体内容。
    • argv[0] 是程序的名称,argv[1] 是第一个参数,argv[2] 是第二个参数,依此类推;

以文件复制程序为例:

#include <stdio.h>
#include <stdlib.h>
void copyFile(const char *src, const char *dst, size_t bufferSize) {
	FILE *sourceFile = fopen(src, "rb");
	FILE *destinationFile = fopen(dst, "wb");
	if (!sourceFile || !destinationFile) {
		perror("无法打开文件");
		exit(EXIT_FAILURE);
	}
	char *buffer = (char *)malloc(bufferSize);
	if (!buffer) {
		perror("内存分配失败");
		exit(EXIT_FAILURE);
	}
	size_t bytesRead;
	while ((bytesRead = fread(buffer, 1, bufferSize, sourceFile)) > 0) {
	fwrite(buffer, 1, bytesRead, destinationFile);
	}
	free(buffer);
	fclose(sourceFile);
	fclose(destinationFile);
}
	
int main(int argc, char *argv[]) {
	if (argc != 3 && argc != 4) {
	printf("用法:%s <源文件> <目标文件> [缓冲区大小]\n", argv[0]);
	return 1;
	}
	const char *sourceFile = argv[1];
	const char *destinationFile = argv[2];
	size_t bufferSize = 1024; // 默认缓冲区大小
	if (argc == 4) {
		bufferSize = atol(argv[3]); // 将第三个参数转换为长整型
		//`atol` 是 C 标准库中的一个函数,用于将字符串转换为长整型long,它的名字是 "ASCII to long" 的缩写,作用是从字符串中解析出整数部分,并返回对应的 `long` 类型的值;
	}
	copyFile(sourceFile, destinationFile, bufferSize);
	return 0;
}

假设编译后的程序名为 copyfile:

  • 复制文件 file1.txt 到 file2.txt,使用默认缓冲区大小:
./copyfile file1.txt file2.txt
  • 复制文件 file1.txt 到 file2.txt,指定缓冲区大小为 4096 字节:
./copyfile file1.txt file2.txt 4096
  • 如果参数数量不正确,程序会输出用法信息:
./copyfile file1.txt

枚举类型

1. 枚举类型的基本特性

  • 枚举类型(enum)是一种用户定义的数据类型,用于表示一组命名的整数常量。
  • 枚举常量默认从0开始递增,但可以显式赋值。
  • 枚举类型在C语言中本质上是 int 或 unsigned int 类型。

2. 遍历枚举类型的条件

  • 枚举常量必须连续赋值,才能通过循环遍历。
  • 如果枚举常量不连续,遍历会导致无效值。

3. 遍历枚举类型的代码示例

#include <stdio.h>

// 定义枚举类型
enum DAY {
    MON = 1,  // 1
    TUE,      // 2
    WED,      // 3
    THU,      // 4
    FRI,      // 5
    SAT,      // 6
    SUN       // 7
} day;  // 声明一个枚举变量 day

int main() {
    // 遍历枚举元素
    for (day = MON; day <= SUN; day++) {
        printf("枚举元素:%d \n", day);
    }

    return 0;
}

输出:

枚举元素:1
枚举元素:2
枚举元素:3
枚举元素:4
枚举元素:5
枚举元素:6
枚举元素:7

4. 什么时候不能遍历?

如果枚举常量的值不是连续的,遍历会导致访问无效的值;

如下:

enum Status {
    OK = 0,
    Error = 5,
    Timeout = 10
};

for (enum Status s = OK; s <= Timeout; s++) {
    printf("%d\n", s);
}

输出:

0
1
2
3
4
5
6
7
8
9
10

5. 将整数转换为枚举

#include <stdio.h>

int main() {
    // 定义枚举类型
    enum day {
        saturday,  // 0
        sunday,    // 1
        monday,    // 2
        tuesday,   // 3
        wednesday, // 4
        thursday,  // 5
        friday     // 6
    };

    // 声明枚举变量
    enum day weekend;

	//将整数转换为枚举
	weekend = ( enum day ) a; //类型转换
	//weekend = a; //错误

    // 或者可以直接使用枚举常量赋值
    weekend = sunday;

    // 输出 weekend 的值
    printf("weekend: %d\n", weekend);  // 输出: weekend: 1

    return 0;
}

typdef和define的区别

define是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:

  • define 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如可以定义 1 为 ONE。
  • typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的;

 是 C 语言中的一种预处理指令,可以理解为一种“代码替换工具”。它的作用是在编译之前,把代码中的某些内容替换成另外的内容;

#define PI 3.14159

可变参数

在C语言中,可变参数函数允许你定义一个可以接受任意数量参数的函数。这种函数通常用于处理参数数量不确定的情况,比如printfscanf等标准库函数;

printf("%d %f %s", 10, 3.14, "hello");

这里 printf 的参数数量是不固定的,这就是可变参数函数。

解释:

  • va_list<stdarg.h>中定义的一个类型,本质上是一个指针,用来存贮可变参数列表的信息;
  • va_start用来初始化可变参数列表,第一个参数放va_list定义的变量args,第二个参数放sum函数中最后一个固定变量count,意思是从count后面的位置开始,初始化可变参数列表;
  • va_arg的作用是从可变参数列表中获取下一个参数,它的第二参数是下一个参数的类型;
#include <stdio.h>
#include <stdarg.h>

int sum(int count, ...)
{
    va_list args;  // 声明一个“书架”
    int total = 0;

    // 从 count 标记的位置开始,准备拿书
    va_start(args, count);

    // 依次拿书,并累加到 total 中
    for (int i = 0; i < count; i++) {
        int value = va_arg(args, int);  // 拿一本 int 类型的书
        total += value;
    }

    // 拿完书后,清理书架
    va_end(args);

    return total;
}

int main()
{
    // 测试 sum 函数
    printf("Sum of 1, 2, 3 = %d\n", sum(3, 1, 2, 3));
    printf("Sum of 10, 20, 30, 40 = %d\n", sum(4, 10, 20, 30, 40));
}

运行结果:

Sum of 1, 2, 3 = 6
Sum of 10, 20, 30, 40 = 100

内存管理

  • 下面分配的内存都是分配到堆区,内存需要手动分配和释放;
  • 当程序退出时,操作系统会自动释放所有分配给程序的内存,但是,建议在不需要内存时,都应该调用函数free() 来释放内存;
序号函数和描述
1void *calloc(int num, int size);在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是 0;
2void *malloc(int num);在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的
3void *realloc(void *address, int newsize);该函数重新分配内存,把内存扩展newsize;
4free(void *address);上面那些函数动态申请的空间都可以用free释放

代码示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
    char name[100];
    char *description;
    strcpy(name, "Zara Ali");
    /* 动态分配内存 */
    description = (char *)malloc(200 * sizeof(char));
    if (description == NULL)
    {
        fprintf(stderr, "Error - unable to allocate required memory\n");
    }
    else
    {
        strcpy(description, "Zara ali a DPS student in class 10th");
    }
    printf("Name = %s\n", name);
    printf("Description: %s\n", description);
    return 0;
}

运行结果:

Name = Zara Ali
Description: Zara ali a DPS student in class 10th

指针数组和数组指针

指针数组

数组里面存的是指针;

int* arr[3];
// 变成后缀表达式后应变成
arr [3] int*	// arr 是一个有三个元素的数组, 里面的内容是int*,所以arr是一个有3个int*元素的数组

示例

int a = 10, b = 20, c = 30;
int *arr[3] = {&a, &b, &c};  // 每个元素都是指针,指向不同的变量
printf("%d\n", *arr[0]);    // 输出 10
printf("%d\n", *arr[1]);    // 输出 20
printf("%d\n", *arr[2]);    // 输出 30

数组指针

数组指针是一个指针,它指向一个数组。
int (*p)[n]=&arr

int (*arr)[3];
// 变成后缀表达式后应变成
arr * [3] int 	// arr是一个指针,指向有3个int类型的数组

区别于普通指针

假设有一个数组arr{1,2,3,4,5}:

  • int *p = arr

    • 这是一个普通指针,指向数组的第一个元素(arr[0] 的地址);
    • 通过它,可以访问数组中的元素,但无法直接表达“操作整个数组”的含义;
    • p+1等同于p[1],指向数组的第二个元素;
  • int (*p)[5] = &arr;

    • 这是一个数组指针,指向整个数组 arr(数组的首地址,类型为 int[5]);
    • 通过它,可以把数组作为一个整体来操作,而不是仅仅处理单个元素,可以理解为指针的指针;
    • p + 1 跳过整个数组(假设数组大小为 5 个 int,那么 p + 1 实际跳过 5 * sizeof(int)个字节);
    • 可以用 (*p)[i] 访问数组中的第 i 个元素;

数组指针示例

#include <stdio.h>

int main() {
    int arr[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };

    int (*p)[4] = arr;  // p 指向二维数组的首行

    // 访问第一行元素
    printf("%d\n", (*p)[0]);  // 输出 1
    printf("%d\n", (*p)[3]);  // 输出 4

    // 访问第二行元素
    p++;  // p 指向下一行
    printf("%d\n", (*p)[0]);  // 输出 5
    printf("%d\n", (*p)[3]);  // 输出 8

    return 0;
}

如果是三维或者更多维数组,第一维可以不写:

int arr[2][3][4];  // 2 个二维数组,每个 3 行 4 列
int (*p)[3][4];    // p 是一个数组指针,指向包含 3 行 4 列的二维数组
p = arr;           // p 指向三维数组的首二维数组

为什么第一维可以不写?

  • 第一维的大小可以省略
    数组指针声明不需要第一维度的信息,因为数组的首地址已经表明了它的位置;

  • 后续维度的大小不能省略
    因为编译器需要通过后续维度信息来计算数组中元素的内存偏移量;


类比:
把这个三维数组 arr[2][3][4] 想象成一个楼房

  • 楼房有 2 层(第一维,[2])。
  • 每层楼有 3 个房间(第二维,[3])。
  • 每个房间里有 4 个座位(第三维,[4])。
    数组指针 int (*p)[3][4] 的作用是:
  • 指向每一层楼。
  • 只要知道每层楼的布局(有 3 个房间,每个房间有 4 个座位),就可以正确找到所有座位。
  • 第一维 [2] 可以省略,因为直接指向整栋楼,知道从哪里开始就行(每一层楼)。
  • 后面的 [3][4] 不可省略,因为你需要知道每层楼的具体布局,才能找到房间和座位。

复杂指针组合

函数指针数组

void (*f[3])(int); 
// 变成后缀表达式后应变成 
f [3] * (int) void // f3 是一个3个元素的数组,每个元素是一个指针,指向了参数为int类型,返回值为空的函数

example:

void test1(int a)
{
	printf( "test1...\n" );
}

void test2( int a )
{
	printf( "test2...\n" );
}

void test3( int a )
{
	printf( "test3...\n" );
}

void (*pfnArrys[3])(int) = { test1, test2, test3 };

pfnArrys的数组,长度为3,每个元素都是指针,这些函数接受一个类型为int的参数,如下的100,并且这些函数的返回类型都是void;

运行结果:

pfnArrys[0](100);  // 会调用 test1 并打印 "test1..."
pfnArrys[1](100);  // 会调用 test2 并打印 "test2..."
pfnArrys[2](100);  // 会调用 test3 并打印 "test3..."

继续补充

// 返回值是函数指针数组的函数,这个函数没有参数
// 写一个函数返回pfnArrys
void (*(*funname())[3])(int)		// funname () * [3] * (int) void   funame是一个函数'()', 返回值是指针'*',
									// 这个指针指向了有3个元素的数组'[3]', 数组里面的内容是指针'*',这个指针指向一个带有int类型参数的函数                                         //(int),并且返回值是void类型
{
	return &pfnArrys;
}

void (*ptr)(int) = test1;


int main()
{

	(*(*funname() + 1))(2);  //调用test2(2)


	system( "pause" );

	return 0;
}

(*(*funname() + 1))(2)

  • funname() 返回数组指针,指向 pfnArrys 的地址
  • *funname() 解引用得到数组 pfnArrys
  • *funname() + 1 是数组指针偏移,移动到第二个元素(即 test2 的地址)
  • 最外层的 (*)(...)(2) 是调用这个函数指针 //当我们有一个函数指针时,确实需要先解引用再调用,这是因为 C 语言的语法规定,不能直接用指针来调用函数,必须先解引用得到函数本身。
  • 所以这会调用 test2(2)

Typedef简化

来看 typedef void (*sighandler_t)(int);

  • void (*)(int) 是一个函数指针类型,指向一个接受 int 参数并返回 void 的函数。
  • typedef 将这个函数指针类型定义为一个新的类型别名 sighandler_t
    因此,sighandler_t 是一个类型别名,表示一个指向以下函数的指针:
  • 返回值:void
  • 参数:int

C语言的异常处理

代码

include <errno.h>

#include <errno.h> 
#include <stdio.h> 

int main()
{
	FILE* fp;
	
	printf( "value of errno = %d\n", errno );
    
	fp = fopen( "123.txt", "r" );

	printf( "value of errno: %d\n", errno );


	system( "pause" );
	return 0;
}

解释

  1. 头文件引入
    • #include <errno.h>:引入 errno 的定义。
    • #include <stdio.h>:引入标准输入输出函数,如 printf 和 fopen
  2. 主函数 main
    • FILE* fp;:声明一个文件指针 fp,用于指向文件。
  3. 打印初始的 errno 值
    • printf( "value of errno = %d\n", errno );:在程序开始时,打印 errno 的初始值。通常情况下,如果之前没有发生错误,errno 的值是 0
  4. 尝试打开文件
    • fp = fopen( "123.txt", "r" );:尝试以只读模式打开名为 123.txt 的文件。如果文件不存在或无法打开,fopen 会返回 NULL,并且 errno 会被设置为相应的错误代码。
  5. 打印打开文件后的 errno 值
    • printf( "value of errno: %d\n", errno );:在尝试打开文件后,再次打印 errno 的值。如果文件打开失败,errno 将包含一个非零值,表示具体的错误类型。
  6. 暂停程序
    • system( "pause" );:调用系统命令 pause,使程序暂停,等待用户按任意键继续。这个命令通常用于Windows系统,以便在控制台窗口中查看输出结果。
  7. 返回 0
    • return 0;:程序正常结束,返回 0;

errno错误值对照表

errno valueError信息
1Operation not permitted
2No such file or directory
3No such process
4Interrupted system call
5I/O error
6No such device or address
7The argument list is too long
8Exec format error
9Bad file number
10No child processes
11Try again
12Out of memory
13Permission denied

perrnor

  • perror 会根据当前 errno 的值,查找对应的错误描述字符串,并将其打印到标准错误流(stderr)。
  • 如果 str 不为空,perror 会先打印 str,然后打印错误信息。
  • 如果 str 为空,perror 只会打印错误信息。
#include <stdio.h>
#include <errno.h>

int main() {
    FILE *fp;

    // 尝试打开一个不存在的文件
    fp = fopen("nonexistent_file.txt", "r");
    if (fp == NULL) {
        // 使用 perror 打印错误信息
        perror("Error opening file");
    }
    return 0;
}

如果文件 nonexistent_file.txt 不存在,fopen 会失败,并将 errno 设置为 ENOENT(表示“没有这样的文件或目录”)。perror 会输出类似以下内容:

Error opening file: No such file or directory

跳转

setjmp和longjmp

  • goto 只能在同一个函数内跳转;
  • setjmp 和 longjmp 可以在不同函数之间跳转,甚至可以跨越多个函数调用层级;
  • 跳转会破坏程序的正常控制流,过度使用可能导致代码难以理解和维护;

用法

  1. 保存环境
    • 调用 setjmp 时,当前的程序状态(如寄存器、栈指针、程序计数器等)会被保存到 jmp_buf 中。
    • setjmp 返回 0,程序继续执行。
  2. 恢复环境
    • 调用 longjmp 时,程序会跳转回 setjmp 的位置,并恢复之前保存的程序状态。
    • setjmp 返回 longjmp 提供的非零值。
#include <stdio.h>
#include <setjmp.h>

jmp_buf env; // 用于保存程序执行环境

void function() {
    printf("Entering function...\n");
    longjmp(env, 42); // 跳转回 setjmp 的位置,并传递值 42
    printf("This line will not be executed.\n");
}

int main() {
    int ret;

    // 保存当前环境
    ret = setjmp(env);

    if (ret == 0) {
        printf("First call to setjmp, ret = %d\n", ret);
        function(); // 调用函数,可能会触发 longjmp
    } else {
        printf("Returned from longjmp, ret = %d\n", ret);
    }

    return 0;
}

输出

First call to setjmp, ret = 0
Entering function...
Returned from longjmp, ret = 42
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值