目录
1.数组名的理解
int main() {
int arr[] = { 1,2,3 };
printf("%p\n", &arr[0]);
printf("%p\n", arr);
return 0;
}
从结果可以看出,&arr[0] == arr,这是用为数组名就是地址,而且是首元素的地址
但是,arr作为数组名在两种情况下不表示首元素的地址:
- sizeof(数组名),sizeof中单独存放数组名表示求整个数组的大小,单位是字节
- &arr,&arr是&后面直接加上一个数组名,它表示整个数组的地址。(&arr在数值上可能与&arr[0]相同,但是本质上是不一样的,即&arr[0] == arr != &arr )
除此之外,数组名都表示首元素的地址。
其实arr与&arr的区别不在于直接打印的数值上,而在于运算上,
int main() {
int arr[] = { 1,2,3 };
printf("%p\n", &arr[0]);
printf("%p\n", &arr[0]+1);
printf("%p\n", arr);
printf("%p\n", arr+1);
printf("%p\n", &arr);
printf("%p\n", &arr+1);
return 0;
}
可以看出,arr和&arr[0]加一的结果都是跳过4个字节,而&arr则跳过12个字节(E8-F4 =(15 * 16 ^ 1 + 4 * 16 ^ 0)- ( 14*16^1 + 8 *16 ^0) =12).
&arr表示整个数组,加一表示跳过整个数组,数组一共3个int类型的数据,所以一次跳过12个字节。
2.使用指针访问数组
数组可以使用下标引用操作符来访问,比如:
int ret = arr[9];
既然数组名相当于地址,那么还可以这样访问:
int* p = &arr[9];
int ret = *p;
3.一维数组传参的本质
以前将数组作为参数传递给函数时需要将数组的数据个数一起传递给函数,那么,不传递数据个数可以吗?
void test(int arr[]) {
int sz2 = sizeof(arr) / sizeof(arr[0]);
printf("sz1 = %d\n", sz2);
}
int main() {
int arr[] = {1,2,3,4,5};
int sz1 = sizeof(arr) / sizeof(arr[0]);
printf("sz1 = %d\n", sz1);
test(arr);
return 0;
}
从结果来看是不行的,
其实,将数组作为参数是将数组的首元素的地址作为参数传递给函数
所以,在函数内部使用sizeof(arr)实际上计算的是一个地址的大小。正因为参数部分的本质是指针,所以不能不传数据个数。
4.二级指针
指针变量也是变量,是变量就有地址,所以二级指针是用来存放一级指针变量的地址的
画图说明:
对于二级指针的运算:
- *ppa通过对ppa中的地址解引用,找到的是pa,*ppa访问的就是p
- **ppa先对ppa解引用得到pa然后对pa解引用得到a
5.指针数组
存放整型的数组叫做整型数组
存放字符的数组叫做字符数组
那么存放指针的数组就叫做指针数组
6.字符指针变量
在指针的类型中有一种指针类型叫做字符指针char*
还有一种使用方法:
int main() {
const char* p = "abcdef";
printf("%s\n", p);
return 0;
}
这种表示方法相当于将字符串看作为一个字符数组,指针变量p存放的不是字符串而是首字符’a‘的地址。
如果两个指针指向同一个字符串,那么不会开辟两个空间来存放,也就是说两个指针指向的是同一个内存
7.数组指针变量
存放整型数据地址的指针叫做整型指针
存放字符型数据的指针叫做字符指针
那么存放数组的地址的指针叫做数组指针
指针数组是数组,而数组指针是变量
int (*P)[5];
p先于*结合说明p是一个指针,再于【】和 int结合说明他是一个数组指针,数组元素类型是int。
数组自指针的初始化:
int main() {
int arr[10] = { 0 };
int(*p)[10] = &arr;
return 0;
}
&arr == p
8.二维数组传参的本质
当我们将二位数组作为参数传递给函数时,其实传递的是第一行的地址(不是第一个元素的地址)
void test(int(*p)[3], int r, int c) {
for (int i = 0; i < r; i++) {
for (int j = 0; j < c; j++) {
printf("%d ", *(*(p + i) + j));
}
printf("\n");
}
}
int main() {
int arr[2][3] = { {1,2,3},{2,3,4} };
test(arr, 2, 3);
return 0;
}
因为传递的是第一行元素的地址,那么该地址就该用函数指针来接收。
9.函数指针变量
函数也有地址,那么该地址就可以使用指针变量来接受,那么该指针变量就是函数指针变量。
int (*p)(int, int);
可以看出函数名就是函数的地址,也可以使用&函数名来获得函数的地址。
要将函数的地址存放起来,就可以使用函数指针变量
可以使用函数指针变量来调用函数
void test() {
printf("haha\n");
}
int main() {
int(*p)() = test;
p();
return 0;
}
10.函数指针数组
函数指针是用来存放函数的地址的,那么有很多函数,要将它们的地址存放在一起,那么就可以使用函数指针数组。
int (*p[10])(int, int);
p先于【】结合说明是一个数组,那么数组的内容就是int (*)().
int Add(int x, int y) {
return x + y;
}
int Sub(int x, int y) {
return x - y;
}
int Mul(int x, int y) {
return x * y;
}
int Div(int x, int y) {
return x / y;
}
void Menu() {
printf("**************************\n");
printf("*****1.加法 2.减法*****\n");
printf("*****3.乘法 4.除法*****\n");
printf("*****0.退出 *****\n");
printf("**************************\n");
}
int main() {
int n = 0;
int num1 = 0, num2 = 0;
int(*p[5])(int, int) = { 0,Add,Sub,Mul,Div };
do {
Menu();
printf("请输入要选择的功能:>");
scanf("%d", &n);
if (n >= 1 && n <= 4) {
printf("\n请输入两个数用于计算:>");
scanf("%d%d", &num1, &num2);
printf("\n计算结果:%d\n", (*p[n])(num1, num2));
}
else if (n == 0) {
printf("已退出\n");
break;
}
else {
printf("选择错误,重新选择:>");
}
} while (n);
return 0;
}
这个代码实现了一个简单的计算器。
int( * p[5])(int, int) = { 0,Add,Sub,Mul,Div };
这段代码就是将四个函数的地址放在一个函数指针数组里面
( * p[n])(num1, num2);
这段代码函数指针数组来调用函数n表示想调用哪个函数,num1和num2就是传递给函数的参数
11.回调函数
回调函数就是一个通过函数指针调用的函数
如果将函数的指针作为一个参数传递给另一个函数,但这个指针被用来调用其所指的函数时,被调用的函数就是回调函数。
void test2() {
printf("hahaha\n");
}
void test1(void (*test2)()) {
printf("haha\n");
test2();
}
int main() {
test1(test2);
return 0;
}
在main函数中,将test2的地址传递给test1,然后test1用函数指针变量来接收,然后调用test2函数,那么test2函数就是回调函数。
12.qsort函数
qsort函数的功能是将任意类型的数据排列,底层原理是快速排序。
#include<stdlib.h>
void com_arr(const void* p1, const void* p2) {
return *(int*)p1 - *(int*)p2;
}
void print(int arr[], int sz) {
for (int i = 0; i < sz; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = { 5,2,1,7,3,4,6,14,8 };
int sz = sizeof(arr) / sizeof(arr[0]);
print(arr, sz);
qsort(arr, sz, 4, com_arr);
print(arr, sz);
return 0;
}
qsort函数需要4个参数,第一个参数是数组首元素的地址,第二个参数是数组中元素的个数,第三个参数是数组中数据类型的大小,第四个参数是一个函数,作用是实现排序这种数据的方法。
在上述代码中排序整型数组中的数据,那么第四个参数就可以让两个整数相减,返回负数表示第一个元素小于第二个元素,反之大于。
13.使用回调函数模拟实现qsort函数
我们可以采用冒泡排序的方式来实现qsort函数
int Method(const void* p1, const void* p2) {//用以判断两个数时候该交换
return (*(int*)p1 - *(int*)p2);
}
void Swap(void* p1, void* p2 ,int sz) {
for (int i = 0; i < sz; i++) {
char tmp = *((char*)p1 + i);
*((char*)p1 + i) = *((char*)p2 + i);
*((char*)p2 + i) = tmp;
}
}
void Maopao(void* arr, int sz, size_t num, int (*method)(void*, void*)) {
for (int i = 0; i < sz - 1; i++) {
for (int j = 0; j < sz - 1 - i; j++) {
if (method((char*)arr + j * num, (char*)arr + (j+1) * num) > 0){
Swap((char*)arr + j * num, (char*)arr + (j+1) * num, num);
}
}
}
}
int main() {
int arr[] = { 4,2,6,1,7,5,9,8 };
int sz = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < sz; i++) {
printf("%d ", arr[i]);
}
printf("\n");
Maopao(arr, sz, sizeof(arr[0]), Method);
for (int i = 0; i < sz; i++) {
printf("%d ", arr[i]);
}
return 0;
}