文章目录
Tips
1.数组名就是数组的首元素地址(但有两个例外)
- sizeof(数组名),这里数组名表示整个数组,sizeof计算的是整个数组的大小.
- &数组名,这里数组名也表示整个数组,&数组名取出的是整个数组的大小.
2.指针类型决定了指针加减整数的步长和解引用操作时的权限
3.[ ] 下标引用操作符
[ ] 操作数:一个数组名 + 一个索引值
- 操作数交换一下位置是可以的.(仅做科普,实际不要这么写)
- 常量字符串返回的是首元素的地址,所以可以这么用(也是仅做科普)
4.void*指针
- 不能进行解引用操作,也不能进行±整数操作
- 可以用来存放任意类型数据的地址
字符指针
str3 和 str4 指向同一块空间.
函数指针
主要有两个使用场景
- 使用函数指针实现转移表的效果,降低代码的圈复杂度
- 使用函数指针达成"回调函数"效果(在qsort中指定比较规则)
和数组指针类似.先看看数组指针.
pa就是数组指针变量.
而函数名(&函数名)就是函数的地址
所以例子中函数指针指向 Add 和 &Add 都行.(甚至可以不解引用或者很多次解引用使用)
看两个比较复杂的例子
1.void (*signal(int, void (*)(int)))(int);
这个代码是声明 signal 函数
函数参数有两个
- 第一个是int类型
- 第二个是函数指针类型void (* )( int )
返回类型也是函数指针类型,该类型是void (* )( int )2.
( * (void( * )( ))0)( );
将0强制类型转换成void(* )( )函数指针类型
解引用
最右边那个括号是函数的参数
例1可以这样理解(但语法是错的)
void(*)(int) signal(int, void(*)(int));
可以用typedef 重命名一下void(*)(int)
typedef void(*p)(int);
这样例1就等于p signal(int, p);
回调函数
回调函数就是一个通过函数指针调用的函数.如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说是回调函数.
模拟实现qsort函数
qsort是一个库函数,使用快速排序的方式对数据进行排序(这里我们采用冒泡排序的方式)
- void* base : 待排序数据的起始地址
- size_t num : 待排序数据的元素个数
- size_t size : 待排序数据的一个元素大小,单位是字节
- int ( * compar)(const void* ,const void* ) : 函数指针 - 指向了一个比较函数,用来比较2个元素
int( * compar)(const void* e1,const void* e2)
- e1指向的元素>e2指向的元素,返回>0的数字
- e1指向的元素==e2指向的元素,返回0
- e1指向的元素<e2指向的元素,返回<0的数字
代码实现
Swap函数交换元素
e1是前一个元素,e2是紧接的下一个元素
如果设置e1>e2返回大于0则交换两数(升序),反之降序
void Swap(char* buf1, char* buf2, size_t size)
{
int i = 0;
for (i = 0; i < size; i++)
{
char temp = *buf1;
*buf1 = *buf2;
*buf2 = temp;
buf1++;
buf2++;
}
}
void my_qsort(void* base, size_t num, size_t size, int(*compar)(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 (compar((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
{
Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}
- 整数升序my_qsort第四个参数:
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
整数倒序(将e1,e2交换一下顺序)
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e2 - *(int*)e2;
}
- 定义结构体
struct Stu
{
char name[20];
int age;
};
按name升序(只是英文字母,按ascll码的大小)
int cmp_stu_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name , ((struct Stu*)e2)->name);
}
按age升序
int cmp_stu_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
数组形参的两种形式(以二维为例)
令arr是二维数组的数组名,是首元素( &arr[0] )的地址
1.二维数组的形式(也可以写成int arr[][5]
)
2.指针形式
面试题
(1)
#include<stdio.h>
int main()
{
const char* a[] = { "work","at","alibaba" };
const char** pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
pa++跳过一个char*指针,指向了"at"
(2)
int main()
{
int a[5][5];
int(*p)[4];
p = a;
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
%p是打印地址,认为内存中存储的补码就是地址.
&p[4][2] - &a[4][2] 等于-4
-4的补码是FFFFFFFFFFFFFFFC
(3)
//在x86的环境下演示
//在这里结构体的大小是20字节
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
int main()
{
p = (struct Test*)0x100000;
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
- 16进制的14就是20
- 第二个是强转成整型,不是指针
(4)
int main()
{
int a[3][4] = { 0 };
printf("%zd\n", sizeof(a));//48
printf("%zd\n", sizeof(a[0]));//16
printf("%zd\n", sizeof(a[0]+1));//4/8 - 第一行第一个元素的地址&a[0][1]
//a[0]并非单独放在sizeof内部,也没有&,所以a[0]表示第一行这个一维数组首元素地址
printf("%zd\n", sizeof(*(a[0]+1)));//4 - int类型
printf("%zd\n", sizeof(a+1));//4/8
//a+1 就是第二行的地址,类型是:int (*)[4]
printf("%zd\n", sizeof(*(a+1)));//16 - 第二行,也就是a[1]
//sizeof(a[1]) - 数组名单独放在sizeof内部,计算的是第二行的大小
printf("%zd\n", sizeof(&a[0]+1));//4/8
//a[0]是第一行的数组名,&a[0]取出的是第一行这个一维数组的地址,类型就是int(*)[4]
//&a[0]+1 就是第二行的地址,类型是int(*)[4]
printf("%zd\n", sizeof(*(&a[0] + 1)));//16 - 跟*(a+1))一样
printf("%zd\n", sizeof(*a));//16
printf("%zd\n", sizeof(a[3]));//16 - sizeof没有访问空间,所以不算越界访问
return 0;
}
变长数组与柔性数组
变长数组(可以用变量确定元素个数)
虽然C语言C99支持变长数组,但是变长数组不能初始化.
柔性数组
C99中,结构体中的最后一个元素允许是未知大小的数据,这就叫做柔性数组成员
特点:
- 柔性数组成员前面必须至少有一个其他成员
- sizeof返回的结构体大小不包括柔性数组
- 包含柔性数组的结构体用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小
两种定义方式(arr[] 和 arr[0])
#include<stdlib.h>
#include<stdio.h>
struct s
{
char a;
int arr[];//或者int arr[0]
};
int main()
{
int i = 0;
struct s* p = (struct s*)malloc(sizeof(struct s) + 10 * sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
p->a = 97;
printf("%c\n", p->a);
for (i = 0; i < 10; i++)
{
p->arr[i] = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", p->arr[i]);
}
free(p);
p = NULL;
return 0;
}
不用柔性数组
#include<stdio.h>
#include<stdlib.h>
struct S
{
int n;
int* arr;
};
int main()
{
struct S* ps = (struct S*)malloc(sizeof(struct S));
if (ps == NULL)
{
perror("malloc");
return 1;
}
ps->arr = (int*)malloc(5 * sizeof(int));
if (ps->arr == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
ps->arr[i] = i;
for (i = 0; i < 5; i++)
printf("%d ", ps->arr[i]);
int* ptr = (int*)realloc(ps->arr, 10 * sizeof(int));
if (ptr != NULL)
{
ps->arr = ptr;
ptr = NULL;
}
for (i = 5; i < 10; i++)
ps->arr[i] = i;
for (i = 0; i < 10; i++)
printf("%d ", ps->arr[i]);
//注意顺序
free(ps->arr);
ps->arr = NULL;
free(ps);
ps = NULL;
return 0;
}
上述代码都可以完成相同的功能,但柔性数组方便内存释放,访问速度更快,减少内存碎片