文档注释
/**
为文档注释
/**
*函数功能的说明
*对参数的描述
*返回什么
*/
32位机器和64位机器上C语言数据类型大小
数据类型 | 32位 | 64位 |
---|---|---|
bool | 1 | 1 |
char | 1 | 1 |
unsigned char | 1 | 1 |
short int | 2 | 2 |
int | 4 | 4 |
指针 | 4 | 8 |
unsigned int | 4 | 4 |
float | 4 | 4 |
long | 4 | 8 |
unsigned long | 4 | 8 |
double | 8 | 8 |
long long | 8 | 8 |
二者有区别的是指针 和long 、unsigned 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语言的泛型
- 函数中的
void*
可以接受任意指针类型,用于泛型转换; typedef
用来定义一个类型,下面的例子中就是用typedef
来定义一个出传入两个void*
参数,返回一个整形的Compare
类型;- 函数的声明只写参数类型,不写参数名;
普通函数
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
是用于标记字符串的结束;
相关函数:
序号 | 函数 & 作用 |
---|---|
1 | strcpy(s1, s2);复制字符串 s2 到字符串 s1; |
2 | strcat(s1, s2);连接字符串 s2 到字符串 s1 的末尾; |
3 | strlen(s1);返回字符串 s1 的长度; |
4 | strcmp(s1, s2);如果 s1 和 s2 是相同的,则返回 0;如果 s1s2 则返回大于 0; |
5 | strchr(s1, ch);返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置; |
6 | strstr(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语言中,可变参数函数允许你定义一个可以接受任意数量参数的函数。这种函数通常用于处理参数数量不确定的情况,比如
printf
和scanf
等标准库函数;
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() 来释放内存;
序号 | 函数和描述 |
---|---|
1 | void *calloc(int num, int size); 在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0 。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是 0 ; |
2 | void *malloc(int num); 在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的 |
3 | void *realloc(void *address, int newsize); 该函数重新分配内存,把内存扩展到 newsize ; |
4 | free(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;
}
解释
- 头文件引入:
#include <errno.h>
:引入errno
的定义。#include <stdio.h>
:引入标准输入输出函数,如printf
和fopen
。
- 主函数
main
:FILE* fp;
:声明一个文件指针fp
,用于指向文件。
- 打印初始的
errno
值:printf( "value of errno = %d\n", errno );
:在程序开始时,打印errno
的初始值。通常情况下,如果之前没有发生错误,errno
的值是0
。
- 尝试打开文件:
fp = fopen( "123.txt", "r" );
:尝试以只读模式打开名为123.txt
的文件。如果文件不存在或无法打开,fopen
会返回NULL
,并且errno
会被设置为相应的错误代码。
- 打印打开文件后的
errno
值:printf( "value of errno: %d\n", errno );
:在尝试打开文件后,再次打印errno
的值。如果文件打开失败,errno
将包含一个非零值,表示具体的错误类型。
- 暂停程序:
system( "pause" );
:调用系统命令pause
,使程序暂停,等待用户按任意键继续。这个命令通常用于Windows系统,以便在控制台窗口中查看输出结果。
- 返回 0:
return 0;
:程序正常结束,返回0
;
errno错误值对照表
errno value | Error信息 |
---|---|
1 | Operation not permitted |
2 | No such file or directory |
3 | No such process |
4 | Interrupted system call |
5 | I/O error |
6 | No such device or address |
7 | The argument list is too long |
8 | Exec format error |
9 | Bad file number |
10 | No child processes |
11 | Try again |
12 | Out of memory |
13 | Permission 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
可以在不同函数之间跳转,甚至可以跨越多个函数调用层级;- 跳转会破坏程序的正常控制流,过度使用可能导致代码难以理解和维护;
用法
- 保存环境:
- 调用
setjmp
时,当前的程序状态(如寄存器、栈指针、程序计数器等)会被保存到jmp_buf
中。 setjmp
返回0
,程序继续执行。
- 调用
- 恢复环境:
- 调用
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