C语言知识点 – 指针
文章目录
一、指针
1.指针的大小
指针的大小在32位平台是4个字节,在64位平台是8个字节。
2.指针运算
1.指针 + - 整数
会跳过n个数据类型大小的空间。
2.指针 - 指针
指针 - 指针得到的是指针之间的元素个数,而不是字节个数,前提是两个指针必须指向同一块地址空间。
用指针 - 指针的方法实现my_strlen()函数,代码如下:
int my_strlen(char* str)
{
char* start = str;
while ('\0' != *str)
{
str++;
}
return str - start;
}
3.指针的关系运算
p1 < p2
3.二级指针
二级指针中存放的是指针变量的地址,指向的是指针变量。
int a = 9;
int* pa = &a;
int** ppa = &pa;//二级指针
**ppa = 2;
4.指针数组
指针数组是存放指针的数组。
int* arr[5];//arr数组中有5个元素,每个元素都是int*类型
5.常量字符串
int main()
{
const char* p1 = "abcdef";
const char* p2 = "abcdef";
char a1[] = "abcdef";
char a2[] = "abcdef";
return 0;
}
p1和p2指向的是常量字符串,是不能被修改的,需要用const来修饰,存放在常量区,只会创建一份一摸一样的字符,所以p1和p2指向的地址是一样的。
而a1和a2只是用字符串来创建了两个数组,数组之间的空间是独立的,存放在栈区,所以a1和a2指向的地址是不同的。
6.数组指针
int* p1[5];//p1先和[]结合,是数组,指针数组
int (*p2)[5];//p2先和*结合,是指针,指向数组,数组有5个元素,每个都是int型
打印二维数组元素:
//当二维数组的数组名表示首元素地址时,表示的是首行的地址,所以可以写成数组指针的形式
void PrintArr(int (*pa)[5], int row, int col)
{
int i = 0;
for(i = 0; i < row; i++)
{
int j = 0;
for(j = 0; j < col; j++)
{
printf("%d ", *(*(pa + i) + j));
//pa + i指向的是第i行,解引用就取出了第i行的地址,也就是第i行的数组名
//*(pa + i) + j是第i行第j个元素的地址,解引用就是这个元素
//也可以直接写成pa[i][j]
}
}
}
判断:
int (*pa1)[10];
//pa1先和*结合,是数组指针,指向有10个int的数组
int (* pa2[10])[5];
//pa2先和[]结合,是指针数组,去掉数组名和元素个数,剩下的就是数组类型
//数组类型是:int(* )[5],每一个元素都是一个数组指针
7.数组传参
1.一维数组传参
void test1(int arr[])
{}
void test2(int arr[10])
{}
void test3(int *p)
{}
//这三种传参形式都是正确的,因为一维数组的数组名就是首元素的地址
2.二维数组传参
void test1(int arr[3][4])
{}
void test2(int arr[][4])//列不能省略
{}
void test3(int (*pa)[4])//数组指针
{}
8.函数指针
指向函数的指针。
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = Add;//pf就是指向Add函数的指针
//Add和&Add都表示函数的地址
int ret1 = pf(2, 3);//使用函数指针调用函数,可以不解引用
int ret2 = (*pf)(2, 3);//要解引用的话,一定要放到括号里,不能*pf(2,3)
return 0;
}
9.函数指针数组
代码如下:
int (* pf[4])(int, int) = {Add, Sub, Mul, Div};
//pf先和[]结合,是数组,类型为int(*)(int, int),是函数指针
//去掉数组名,剩下就是数组类型
10.指向函数指针数组的指针
代码如下:
int (* pf[4])(int, int) = {Add, Sub, Mul, Div};//函数指针数组
int (*(*ppf)[4])(int, int) = &pf;//p是指针,指向的类型为int (* [4])(int, int),是函数指针数组
11.回调函数
回调函数就是通过函数指针来调用的函数。如果你把函数指针作为参数传给另一个函数,当这个指针用来调用其所指向的函数时,就构成回调函数。
例如:
void test()
{
printf("hehe\n");
}
void print_hehe(void (*pf)())
{
pf();
}
int main()
{
print_hehe(test);//将一个函数的地址传给另一个函数当作参数
return 0;
}
采用冒泡排序的方式模拟实现qsort:
int cmp_int(const void* e1, const void* e2)//自己编写一个整形比较函数,将地址传给qsort函数当作参数
//void*类型的指针没有固定的类型,可以接受任意类型的指针
{
return (*(int*)e1 - *(int*)e2);//先将e1和e2转换为int*类型的指针,再进行解引用
}
//e1 - e2返回的逻辑代表升序,若使用e2 - e1,则逻辑相反,本应交换的数据不交换,就变成了降序排序
void print(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void swap(char* buf1, char* buf2, int width)//使用char*类型指针逐字节进行数据交换
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void* base, int num, int width, int (*cmp)(const void* e1, const void* e2))
{
int i = 0;
for (i = 0; i < num - 1; i++)//外循环
{
int j = 0;
for (j = 0; j < num - 1 - i; j++)//内循环
{
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)//若cmp函数的返回值大于0,则进行两数交换
//先将base指针强制类型转换成char*类型,一次访问一个字节
{
swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
void test3()
{
int arr[] = { 9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
print(arr, sz);
}
int main()
{
test3();
return 0;
}
12.指针与const
int a = 0;
const int* p1 = &a;
//const放在指针类型前,表示指针所指向的内容不能改变
//即p1所指向的a的值不能被改变
int* const p2 = &a;
//const放在*后,表示指针的指向不能改变
//即p2不能指向其他变量
13.练习
一维数组
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));//16
printf("%d\n", sizeof(a + 0));// 4 / 8 这里a不是上述的两种例外情况,所以a代表数组名,a+0是数组第一个元素地址,
// sizeof(a + 0)表示第一个元素地址的大小
printf("%d\n", sizeof(*a)); // 4 / 8 a表示首元素地址,*a表示第一个元素,sizeof(*a)表示第一个元素的大小
printf("%d\n", sizeof(&a));// 4 / 8 &a取出的是数组的地址,地址大小就是4或8字节
printf("%d\n", sizeof(*&a));// 16 &a是整个数组的地址,解引用后拿到的是整个数组,计算的是整个数组的大小
printf("%d\n", sizeof(&a + 1));// 4 / 8 &a 是整个数组的地址,+1跳过整个数组,指向的是4后面的那个元素的地址
printf("%d\n", sizeof(&a[0]));// 4 / 8 取出的是数组第一个元素的地址
printf("%d\n", sizeof(&a[0] + 1));// 4 / 8 取出的是数组第二个元素的地址
字符数组
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));// 6 sizeof计算的是占用内存空间的大小,不在乎有没有\0
printf("%d\n", strlen(arr));// 随机值 因为strlen计算的是\0之前的字符串长度,而arr数组中没有\0,所以会继续向后找\0
char arr[] = "abcdef"; // [a b c d e f \0]
printf("%d\n", strlen(arr));// 6 数组中\0前有6个字节
printf("%d\n", strlen(arr + 0));// 6 arr+0是首元素地址
printf("%d\n", strlen(*arr));// err *arr是首元素,非法访问
二维数组
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));// 12 * 4= 48 siizeof(a)表示整个数组的大小
printf("%d\n", sizeof(a[0][0]));// 4 第一行第一个元素的大小
printf("%d\n", sizeof(a[0]));// 4 * 4 = 16 a[0]是第一行的数组名,sizeof(a[0])计算的是第一行的大小
printf("%d\n", sizeof(a[0] + 1));// 4 / 8 a[0]作为第一行的数组名,没有单独放在sizeof内部,也没有被取地址,
//所以a[0]就是第一行的第一个元素的地址,加一就是第一行第二个元素的地址
printf("%d\n", sizeof(*(a[0] + 1)));// 4 表示第一行第二个元素
printf("%d\n", sizeof(a + 1));// 4 / 8 a表示首元素的地址,二维数组首元素地址就是第一行的地址,+1表示第二行地址
printf("%d\n", sizeof(*(a + 1)));// 16 对第二行地址解引用,访问的是整个第二行元素,等价于a[1]
printf("%d\n", sizeof(&a[0] + 1));// 4 / 8 a[0]是第一行的数组名,取地址&a[0]取出的是第一行的地址,+1就是第二行地址
printf("%d\n", sizeof(*(&a[0] + 1)));// 16 解引用就是第二行的元素
printf("%d\n", sizeof(*a));// 16 a是首元素地址,就是第一行地址,*a就是第一行;*a -> *(a + 0) -> a[0]
printf("%d\n", sizeof(a[3]));// 16 sizeof是根据数据类型来得出结果的,不会真正访问内存空间
//所以a[3] 相当于 int[3]