1. 传递指向常量的指针(3.2.3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/*
1. 指向常量的指针 & 常量指针
*/
const int *p; // 指向常量的指针
int * const p; // 常量指针
/*
2. 常量取地址
*/
const int limit = 100; // 常量
&limit // 传递给函数 or 赋值, 取指针应该为 指向常量的指针
// 举例说明
int test8() {
const int i = 11;
int *p = &i; //这是非法的,因为 &i 的类型是 const int *,而 p 的类型是 int *。这种赋值会丢弃 const 限定符,编译器通常会发出警告或错误。
// 若编译器警告级别不够 则编译器可能不会报警
*p = 15;
printf("%d", i); // 15
return 0;
}
/*
3. 一个合理的举例
*/
void test3_resign(const int *p1, int *p2) {
*p2 = *p1;
return;
}
int test3() {
const int i = 10;
int j = 12;
test3_resign(&i, &j);
return 0;
}
int test4() {
int i = 10;
int j = 12;
test3_resign(&i, &j);
return 0;
}
/*
4. 一个错误举例
两个问题:
1. 形参 & 实参不匹配
2. 会篡改常量
*/
void test5_resigin(int *p1, int *p2) {
*p1 = 100;
*p2 = 100;
return;
}
int test5() {
const int i = 10;
const int j = 11;
test5_resigin(&i, &j);
return 0;
}
2. 传递指针的指针 (3.2.7)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
为什么要传递指针的指针?
1. 指针是值传递
2. 若想要修改原指针 而不是 指针副本 就需要传递指针的指针
*/
void test7(int ** array, int size, int value) {
*array = (int *) malloc(size * sizeof(int));
if (*array != NULL) {
for (int i = 0; i < size; ++i) {
*(*array+i) = value;
}
}
}
int main() {
int *vector = NULL;
test7(&vector, 5, 5);
...
free(vector);
}
3. 函数指针(3.8)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/*
1. 函数指针的使用
*/
// 基本使用
void (*f_ptr)(int);
f_ptr = test8;
f_ptr(1);
// typdef
typedef void (*f_ptr)(int);
f_ptr f_p;
f_p = test8;
f_p(1);
/*
2. 传递函数指针
*/
typedef int (*fptrOperation)(int, int);
int subtract(int num1, int num2) {
return num1 - num2;
}
int add(int num1, int num2) {
return num1 + num2;
}
int compute(fptrOperation operation, int num1, int num2) {
return operation(num1, num2);
}
int main() {
printf("%d, \n", compute(add, 1, 2));
}
/*
3. 函数指针作为返回值
*/
fptrOperation selects(char opcode) {
switch (opcode) {
case '+':return add;
case '-':return subtract;
}
}
int evaluate(char opcode, int a, int b) {
fptrOperation operation = selects(opcode);
return operation(a,b);
}
int main() {
printf("%d\n", evaluate('+', 5,6));
return 0;
}
/*
4. 函数指针数组
*/
// 定义一
typedef int (*operation)(int, int);
operation operations[128] = {NULL};
// 定义二
int (*opreation[128])(int, int) = {NULL};
// 函数指针数组的意义? 128 长度, 指定每个char 值对应的处理函数
operations['+'] = add;
operations['-'] = subtract
/*
5. 函数指针的比较与转换
*/
// 函数指针的比较
operation f1 = add;
if (f1 == add) {
..
}
/*
6. 转换函数指针
*/
// 1. void * 指针一定不能用在函数指针上
// 2. 所以有时会定义一个基指: typedef void (*fptrBase)(); // 无任何参数 & 返回值为void, 配合转换函数指针
// 3. 函数指针的转换请谨慎使用
4. 字符串指针
1
2
3
4
5
6
7
/*
1. char & 'a'
*/
printf("%d\n", sizeof(char)); // 输出 1
printf("%d\n", sizeof('a')); // 输出 4(在大多数系统中)
// 1. C语言早期设计时,为了兼容字符处理和算术运算的效率,将字符常量视为 int 类型。
// 2. 字符常量:'a' && 字符串字面量:"a"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 字符串初始化操作
// 1. 从字面量池复制
char s[] = "hello jay" // 两份内容 数组一份 字面量池一份 s可更改
// 2. 赋值不合法
char header2[];
header2 = "hello jay"; // 不可把字面量指针赋值给数组名
// 3. 堆中申请内存 & 拷贝 两份内容
char *s = (char*)malloc(strlen("hello jay")+1);
strcpy(header, "hello jay");
// 4. char 指针
char *s = "hello jay"; // 无字符串副本
5. 结构体指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 1. 结构体指针中间会有填充, 请谨慎使用**指针的算数运算**
// 2. 一般使用 offsetof 来看偏移量
struct example {
char a; // 1字节 (地址0)
// 需要3字节填充使int对齐到4
int b; // 4字节 (地址4-7)
short c; // 2字节 (地址8-9)
// 需要2字节填充使整个结构体是4的倍数(对于数组)
}; // 总共12字节
int main() {
printf("各字段原始大小: char=%zu, int=%zu, short=%zu\n",
sizeof(char), sizeof(int), sizeof(short));
printf("结构体总大小: %zu\n", sizeof(struct sample));
printf("各字段偏移量: a=%zu, b=%zu, c=%zu\n",
offsetof(struct sample, a),
offsetof(struct sample, b),
offsetof(struct sample, c));
return 0;
}
/*
各字段原始大小: char=1, int=4, short=2
结构体总大小: 12
各字段偏移量: a=0, b=4, c=8
*/
6. 结构体池
1
2
3
4
/*
结构体池是我自己创造出来的词汇 ...
核心意义还是: 创建一个结构体数组, get & set 获取结构体指针, 减少 malloc & free 开销。
*/
7. static 函数问题
1
2
3
4
5
6
7
8
9
10
11
12
// 1. 防卫式声明的意义
- 仅防止同一个编译单元(如一个.c文件)多次包含同一头文件。
- 例如,如果 a.c 直接或间接多次包含 utils.h,防卫式声明能确保只有一份定义。
// 2. 强弱符号
- 如果存在多个同名强符号,链接器会报错(重复定义)。
- 如果存在一个强符号和多个弱符号,链接器选择强符号。
- 如果全是弱符号,链接器随机选择一个(或报错,取决于编译器)。
// 3. 一般建议
.h 中写函数声明。
static 函数只出现在.c 源文件中(哪怕声明也不要出现在头文件中)。
8. 别名 & restrict 问题
1
https://www.cnblogs.com/thammer/p/10663085.html