关于指针
二级指针避免内存泄露
错误的代码
void tst(char *i) {
i = malloc(100); // 这个 i 运行完毕后被清空了
}
int main() {
char *p = NULL;
tst(p);
strcpy(p, "hello"); // 这个 p 仍然为0
free(p); // 释放不是 malloc 分配的内存
return 0;
}
正确的代码:用二级指针
void tst(char **i) {
*i = malloc(100);
}
int main() {
char *p = NULL;
tst(&p);
strcpy(p, "hello");
free(p);
return 0;
}
零碎知识
char a = '\72'; // 表示 8 进制的ascii码
char s[5] = {"abc"}; // 也是合法的
int *a, b; // a 是个指针,b是int类型
int (*p)[5]; // 指针指向一个长度为5的整形数组
int *p[5]; // 定义一个长度为5的数组,数组的元素都是指针
函数的传参是如何进行的?
int sum(int x,int y);
,调用时,x和y以堆栈的形式复制了一份,当函数运行完毕后,复制的这一份数据自动释放。
如何防止频繁申请内存?
- 如果程序中有很多次
malloc(8)
。那么可以事先malloc(1024*16)
,然后立即释放掉。这样可以避免频繁向系统要内存。
进阶
数组名不是指针
int arr[] = {1, 2, 3, 4, 5}; // arr 是数组名
int *p = (int *) &arr; // p 是指针
printf("%ld != %ld\n", sizeof(arr), sizeof(p));
// 1. 一个是数组的大小,为4*5;一个是指针的大小,为8
// 2. +1后的指向不同
printf("%p,%p\n", p, p + 1);// +1移动一个int
printf("%p,%p", &arr, &arr + 1); // +1移动5*int
// 3. 数组名是指针常量,不可更改
// arr = NULL; //错的
指针作为入参
int func(int arr[]){ // 等价于 int *arr
}
typedef
指针的一些梳理
- 指针常量、指向常量的指针,已经梳理好
零碎
int a[10]
// ?实际上是定义了一个指针?指向数组的开头
取数 a[3]
实际上是 *(a+3)
指针
// 方法1
char a[10] = "hello";
// 上面等价于:
char a[10] = {'h', 'e', 'l', 'l', 'o', 0};
// 上面是初始化一个字符数组
char *b = "hello";
// 上面是初始化一个字符串常量
// 方法2
char a1[10];
char *a2;
// 1. sizeof(a) 结果不一样
// 2. 初始化方式不一样
// a1 = "hello" 是错的
a2 = "hello"; // 直接就可以赋值,
// 必须用这个:
strcpy(a1, "world");
// 但你不能对指针 a2 用 strcpy,memset 之类的,但是:
// 1. 第二个变量可以是指针,表示copy的是指针指向的内容
// 2. 如果用 malloc 分配内存,就可以给指针赋值:
char *a2;
p = malloc(10);
strcpy(p, "hello");
free(p)
// 下面这个函数,多次调用同一个,也不会有错误
// 因为1)传入的参数都是拷贝,这里是指针的拷贝,因此对指针做自增只作用于函数内。但它指向的内存却可以去修改
// 2)也没有对指针指向的内存做改动
int strlen(char *string) {
int length = 0;
while (*string++ != 0) {
length += 1;
}
return length;
}
char *s = “hello” 定义之后,不能改动元素,但 int */char a[] 都是可以改的。
是不是这样解释: char *s = “hello” 是指向的字符串常量,因此不能改动
char *s = "hello";
s[1] = 'c'; // 报错
指针和向量的区别
// 这两个不一样
int a[5]; // 内存中创造一个空间,然后创建一个数组名,它的值是一个常量,指向这段空间
int *b; // 仅仅是创建一个指针本身的内存空间,并没有分配 int 内存空间
// *a 合法,但 *b 不合法,因为访问了一个不存在的位置。
指针加法
int arr1[10];
printf("%p = %p= %p\n", arr1, &arr1, &arr1[0]);
printf("%p = %p\n", arr1 + 1, &arr1[1]); // 指向“下一个元素”
printf("%p = %p\n", &arr1 + 1, &arr1[10]); // 指向“下一个模块”
// 对于二维数组,也是一样的
int mat[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
printf("%p = %p\n", mat, &mat[0][0]); // 指向第0行第0个元素: &arr[0][0]
printf("%p = %p\n", mat + 1, mat[1]); // 指向第1行第0个元素: &arr[1][0]
printf("%p\n", &mat + 1); //指向下一块:&arr[2][2] + 1
// 如何用指针指向一个二维数组
int(*p)[3] = mat; // 指向整个数组的指针
printf("%d", *(*(p + 1) + 2));
问题:
- ` ((arr + 1) + 2)
是什么?是
arr[1][2]` - 这是因为
*(arr + 1) + 2
arr[1, 2]
不会报错,它实际上是arr[2]
,导致结果不符合预期
关于溢出
char 在运算中以 int 运算的,例如:
unsigned char a, b;
b = 200;
a = (b * 5) / 5; // 结果是 200,不会因为超过 255 而溢出,因为它们是以 int 运算的
但是,这个会因为溢出而结果错误
unsigned int a = 0xFFFFFFF0;
long b = (a * 2) / 2;
// 要改成这样才能保证结果正确:
long b = ((long) a * 2) / 2;
一些正确,但不应该写的代码
int a[10]={0,1,2};
int c = 2[a];
// 它相当于 *(2+a),也就相当于 *(a+2),也就相当于 a[2];
实现“子类复用父类的方法”
#include <stdlib.h>
#include <stdio.h>
// 基类结构体
typedef struct {
int age;
} Animal;
Animal *Animal_New(int age) {
Animal *animal = malloc(sizeof(Animal));
animal->age = age;
return animal;
}
// 基类方法,下面的代码中,它将会被子类复用
void Animal_say(Animal *animal) {
printf("I'm an animal. Age = %d", animal->age);
}
// 派生类结构体
typedef struct {
Animal *super;
char *name;
} Dog;
// 派生类初始化方法
Dog *Dog_New(char *name, int age) {
Dog *dog = malloc(sizeof(Dog));
dog->super = Animal_New(age);
dog->name = name;
return dog;
}
//实现"复用父类的方法"
void Dog_say(Dog *dog) {
Animal_say(dog->super);
}
int main() {
Dog *dog = Dog_New("Tom", 2);
Dog_say(dog);
return 0;
}
实现“泛型”
用 void *data
来定义输入值,函数中使用 sizeof(data)
来确定数据类型,并且在if中实现各自的算法
void algo(void* data, int size) {
if (size == sizeof(int)) {
int* p = (int*)data;
// int类型算法操作
} else if (size == sizeof(float)) {
float* p = (float*)data;
// float类型算法操作
}
}
还有一些其它方案
- 用宏定义,例如
#define ADD(x, y) ((x) + (y))
可以对 int、float 都有用 - 用 union
项目的代码规范
C 语言的特性要求所有的函数、结构体名字都不能一样,否则编译时会重复,这导致命名会很长、很丑,可以这样
#ifndef MYPROJECT_MY_LIB_H
#define MYPROJECT_MY_LIB_H
#define T My_Project_Stack_T;
//一大堆函数,大量用到 T
//如此,在这个文件中,可以用 T 来表示 My_Project_Stack_T,大大减少了阅读负担
#undef T
#endif //MYPROJECT_MY_LIB_H
ATOM:一种可让相同的字符串只存一次的方案
- 效果:当程序中申请 new 一个字符串,如果这个字符串之前出现过,就把指针指向之前出现过的那个。
- 优点:节约空间
- 注:经常用于字符串,但其它
关键方案
static struct atom {
struct atom *link; // 哈希冲突的,用链表存下来
int len; // 这个字符串的长度
char *str; // 存放这个字符串
} *buckets[2048];
根据字符串的值,决定其hash值,然后分桶
哈希函数有很多,简单的一个:
for(h=0,i=0;i<len;i++){
h=(h<<1) + scatter[(unsigned char) str[i]];
}
// 其中 scatter[256] 是一个随机数组,用 rand.h 生成的,它用来帮助hash值更均匀
// (unsigned char) 是因为有的机器char值是带符号的
哈希表(python中的dict)有类似的实现
struct T{
struct binding{
struct binding *link;
const void *key;
void *value;
} **buckets;
}
int size;