本期介绍:
本期主要讲解C语言指针如何轻松拿下,以各种面试真题来讲解C语言指针就该这么学
一般使用:
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 's';
return 0;
}
#include
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char *str3 = "hello bit.";
const char *str4 = "hello bit.";
if(str1 ==str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if(str3 ==str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
因为str1和str2都是数组,都开辟了一块空间来存储数组,并且表示的都是首元素的地址
str3和str4两个都是字符指针,定义的是同一个字符串的首元素地址,所以相等。并且是常量字符串。而且常量字符串是不能改变的,且只有一份
指针指向同一个字符串的时候,实际会指向同一块内存
而用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块
辨别数组指针和指针数组:
int *p1[10];
int (*p2)[10];
P1和p2又表示什么呢?
由于[]的优先级要高于*号的,因此p1先和[10]结合,返回类型为int**类型,所以p1是一个指针数组
p2先和*结合,说明p2是一个指针变量,然后指向的是一个大小为10个整型的数组。所以p2是一个指针,指向一个数组,叫数组指针
#include
int main()
{
int arr[10] = {0};
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}
&数组名和数组名都表示首元素的地址,但是两者的本质不同
&arr表示整个数组的首地址,即首元素地址
arr表示数组首元素的地址
#include
int main()
{
int arr[10] = { 0 };
printf("arr = %p\n", arr);
printf("&arr= %p\n", &arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr+1= %p\n", &arr+1);
return 0;
}
因为arr表示数组首元素的地址,因此arr+1就表示数组第二个元素的地址
而&arr表示的是整个数组的地址,也是首元素的地址,因此&arr+1表示跳出整个数组的首元素地址
#include
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
return 0;
}
#include
void print_arr1(int arr[3][5], int row, int col)
{
int i = 0;
for(i=0; i<row; i++)
{
for(j=0; j<col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print_arr2(int (*arr)[5], int row, int col)
{
int i = 0;
for(i=0; i<row; i++)
{
for(j=0; j<col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
print_arr1(arr, 3, 5);
print_arr2(arr, 3, 5);
return 0;
}
数组名arr,表示首元素的地址,但是二维数组的首元素是二维数组的第一行,所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址,可以数组指针来接收。
以下代码的含义是什么?
1. int arr[5];
2. int *parr1[10];
3. int (*parr2)[10];
4. int (*parr3[10])[5];
1. 整型数组
2. 整型指针的数组
3. 数组指针,该指针能够指向一个数组,数组10个元素,每个元素的类型是int类型
4. parr3是一个存储数组指针的数组,该数组能 够存放10个数值指针,每个数组指针能够指向一个数组,数组5个元素,每个元素是int类型
#include
1. void test(int arr[])//ok?
{}
2. void test(int arr[10])//ok?
{}
3. void test(int *arr)//ok?
{}
4. void test2(int *arr[20])//ok?
{}
5. void test2(int **arr)//ok?
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}
这里的5个全部ok,全都是正确的(易)
- arr是一个有10个元素的数组,10个元素都是int类型的,当传参传入(1)的时候,传过去的是首元素的地址,而int arr[]就是接受了这个数组的地址,这[]里面的数字可以不写
- 跟(1)差不多,只不过加了[]里面的数字,就是限制了元素个数
- *arr说明是一个指针,而指针就是地址,所以是对的
- arr2是一个由20个元素的数组,20个元素都是int*类型,当传参传入(4)的时候,传过去的也是首元素的地址,而arr[20]就接受了这个地址,且类型是int*类型
- *arr说明是一个指针,指针就是地址,且类型是int*类型
1. void test(int arr[3][5])//ok?
{}
2. void test(int arr[][])//ok?
{}
3. void test(int arr[][5])//ok?
{}
4. void test(int *arr)//ok?
{}
5. void test(int* arr[5])//ok?
{}
6. void test(int (*arr)[5])//ok?
{}
7. void test(int **arr)//ok?
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}
正确ok(1 3 6),错误no(2 4 5 7)
- 简单(不讲解)
- 二维数组传参,参数可以写成数组,但列不能省略
- 行可以不知道,但列要知道
- arr传过去的是第一行的地址,而且是有5个元素的一维数组,而*arr只表示的是第一行的地址。二维数组传参,传过来的是数组首地址 —— 一维数组,不能匹配
- arr[5]接收到的是第一行的地址,放不了,这里是存放指针的数组不能匹配
- *arr表示第一行的地址,加了[5]就是有5个元素的一维数组,即传过来的地址
- **arr其实表示的是首元素的地址,即第一行的首元素的地址,二级指针不能匹配一维数组的地址
总结:二维数组传参,函数形参的设计只能省略第一个[]的数字,因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素,这样才方便运算。
#include
void print(int *p, int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d\n", *(p+i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9};
int *p = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);
return 0;
}
#include
void test(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int*p = &n;
int **pp = &p;
test(pp);
test(&p);
return 0;
}
首先来看一串代码:
#include
void test()
{
printf("hehe\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
输出:
那我们可以得出结论:&函数名 == 函数名,同时我们要知道数组名 != &数组名
因此在调用函数名的地址,可以省略&符号
(*(void (*)())0)();
首先观察代码,我们可以发现里面能突破的只有常数0,如果它代表常数0,那前面的(类型)就是强转类型,发现强转为函数指针类型
void(*)()
void(*)()0
将强转类型拿下后:
调用0地址处的函数,该函数无参,返回类型是void
1. void(*)() - 函数指针类型
2. (void(*)())0 - 对0进行强制类型转换 - 被解释为一个函数的地址
3. *(void(*)())0 - 对0地址进行解引用操作
4. (*(void(*)())0)() - 调用0地址处的函数
代码二:
void (*signal(int , void(*)(int)))(int);
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int main()
{
int(*pf1)(int, int) = Add;
int(*pf2)(int, int) = Sub;
int(*pfArr[2])(int, int) = {Add, Sub};
//pfArr - 函数指针数组 - 存放同类型的函数指针
return 0;
}
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[5])(const char*) = &pfunArr;
return 0;
}
int(*p)(int, int);//函数指针
int(*p2[4])(int, int);//函数指针的数组
int(*(*p3)[4])(int, int) = &p2;//取出的是函数指针数组的地址
//p3就是一个指向函数指针数组的指针
可以这么理解:有一个A函数,这里不是直接去调用A函数,而是先将A函数的地址传给B函数(这里的B函数的参数就是一个函数指针),然后通过B函数去调用A函数时,这样子就被称为回调函数
对于这种不熟悉的函数,不知道如何使用的话可以上查找即https://cplusplus.com/reference/
在上面可以查看到qsort的使用方法
void qsort(void* base, //base里面放的是待排序的第一个元素(对象)的地址.void* 无类型的指针,什么都可以放进去
size_t num, //排序数据元素的个数
size_t size,//数据中一个元素的大小,单位是字节
int (*compar)(const void*, const void*));//是用来比较待排序数据中的2个元素的函数
#include
#include
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
void test1()
{
//整型数据的排序
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
//排序
qsort(arr, sz, sizeof(arr[0]), cmp_int);
print_arr(arr, sz);
}
int main()
{
test1();
return 0;
}
struct Stu
{
char name[20];
int age;
};
int sort_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int sort_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
void test2()
{
//使用qsort函数排序结构体数据
struct Stu s[] = { {"zhangsan", 30}, {"lisi", 34}, {"wangwu", 20} };
//按照年龄来排序
int sz = sizeof(s) / sizeof(s[0]);
//qsort(s, sz, sizeof(s[0]), sort_by_age);
//按照名字比较
qsort(s, sz, sizeof(s[0]), sort_by_name);
}
int main()
{
test2();
return 0;
}
//假设写一个冒泡排序函数,让你排序字符串
//bubble_sort_str();
//
void bubble_sort(int arr[], int sz)
{
升序
int i = 0;
冒泡排序的趟数
for (i = 0; i < sz - 1; i++)
{
//一趟冒泡排序
int j = 0;
for (j = 0; j < sz - i -1; j++)
{
if (arr[j] > arr[j + 1])
{
交换
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
//升序
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
print_arr(arr, sz);
bubble_sort(arr, sz);
print_arr(arr, sz);
return 0;
}
//模仿qsort函数,实现一个冒泡排序的通用算法(可以排各种数据)
struct Stu
{
char name[20];
int age;
};
void Swap(char* buf1, char* buf2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
int i = 0;
//趟数
for (i = 0; i < sz - 1; i++)
{
//一趟的排序
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
//两个元素的比较
//char*是加几就跳过几个字节
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
//交换
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
int sort_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
printf("%d ", arr[i]);
}
}
void test3()
{
int arr[] = { 1,3,5,7,9,2,4,6,8,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
print_arr(arr, sz);
}
void test4()
{
struct Stu s[3] = { {"zhangsan", 30}, {"lisi", 34}, {"wangwu", 20} };
int sz = sizeof(s) / sizeof(s[0]);
bubble_sort(s, sz, sizeof(s[0]), sort_by_age);
//bubble_sort(s, sz, sizeof(s[0]), sort_by_name);
}
int main()
{
test3();
test4();
return 0;
}
数组名的意义:
- sizeof(数组名),这里要数组名单独存放在sizeof内部,这里的数组名才表示整个数组,不然表示的是首元素的地址,这里计算的是整个数组的大小
- &数组名,这里的数组名表示整个数组,取出的是整个数组的地址
- 除此之外所有的数组名都表示首元素的地址
- sizeof里面计算的,只要是地址,那么计算出来就是4或8,和电脑的位数有关
一维数组
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));//16=4*4
printf("%d\n", sizeof(a + 0));//4或8,a+0是第一个地址,因为a+0了,a并不是单独放在sizeof内部,那么a就表示首元素的地址,+0那么还是首元素的地址,那就是4或8,看电脑位数
printf("%d\n", sizeof(*a));//4, a也是表示首元素的地址,解引用后就是1
printf("%d\n", sizeof(a + 1));//4或8,a+1就是第二个元素的地址
printf("%d\n", sizeof(a[1]));//4
printf("%d\n", sizeof(&a));//4或8,&a虽然是整个数组的地址,但是也是地址,因此sizeof计算的就是一个地址的大小
printf("%d\n", sizeof(*&a));//16,&a是整个数组的地址,解引用后找到的就是整个数组,那么就是16
&a -- int(*p)[4] -- &a
printf("%d\n", sizeof(&a + 1));//4或8,&a表示整个数组的地址,+1跳过一个数组,下一块空间的地址,还是地址
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", strlen(arr));//随机值,arr首元素地址,往后数,但没有\0,因此随机值
printf("%d\n", strlen(arr + 0));//随机值,arr首元素地址,+0还是首元素地址,因此随机
printf("%d\n", strlen(*arr));//错误error,首元素的地址,解引用就是字符a,传过去就是97,不是个合法的地址
printf("%d\n", strlen(arr[1]));//错误error,b为98,同上
printf("%d\n", strlen(&arr));//随机值,整个数组的地址
printf("%d\n", strlen(&arr + 1));//随机值,跳过一个数组的地址
printf("%d\n", strlen(&arr[0] + 1));//随机值,b的地址
error的情况:在模拟strlen函数的时候,我们可以知道 int my_strlen(const char* str)它的参数是一个指针,应该接收地址,而这里传的是一个字符,因此error。
调试可以发现错误
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));//6,
printf("%d\n", sizeof(arr + 0));//4或8,arr表示首元素地址,+0还是首元素地址,还是地址
printf("%d\n", sizeof(*arr));//1,因arr是首元素的地址,*arr解引用就是a,
printf("%d\n", sizeof(arr[1]));//1,arr[1]就是b
printf("%d\n", sizeof(&arr));//4或8,取出这个地址
printf("%d\n", sizeof(&arr + 1));//4或8,&arr是整个数组的地址,+1跳到了f后面的地址,但还是地址
printf("%d\n", sizeof(&arr[0] + 1));//4或8,&arr[0]就是a的地址,+1就是b的地址,还是地址
char arr[] = "abcdef";
[a,b,c,d,e,f,\0]
printf("%d\n", strlen(arr));//6,首元素地址
printf("%d\n", strlen(arr + 0));//6,首元素地址
printf("%d\n", strlen(*arr));//error,将a传过去了,97作为地址有问题
printf("%d\n", strlen(arr[1]));//error,同上,只不过传的是98的地址
printf("%d\n", strlen(&arr));//6,起始元素的地址,还是从首元素开始
printf("%d\n", strlen(&arr + 1));//随机值,跳出整个数组的地址,\0也跳过去了,所以就是随机值
printf("%d\n", strlen(&arr[0] + 1));//5,b的地址,从b开始数
printf("%d\n", sizeof(arr));//7,总大小,算\0
printf("%d\n", sizeof(arr + 0));//4或8,首元素地址+0还是首元素地址,还是地址
printf("%d\n", sizeof(*arr));//1,解引用就是字符a
printf("%d\n", sizeof(arr[1]));//1,解引用就是字符b
printf("%d\n", sizeof(&arr));//4或8,整个数组的地址,还是地址
printf("%d\n", sizeof(&arr + 1));//4或8,跳过整个数组的地址,还是地址
printf("%d\n", sizeof(&arr[0] + 1));//4或8,b的地址,还是地址
char* p = "abcdef";
a b c d e f \0,p存的是a的地址
printf("%d\n", strlen(p));//6
printf("%d\n", strlen(p + 1));//5
printf("%d\n", strlen(*p));//error,*p找到的是a,传进去是97,会出错
printf("%d\n", strlen(p[0]));//error,同上理
printf("%d\n", strlen(&p));//随机值,找的是p的首地址,p里面存的只是a的地址,因此没有\0,所以随机
printf("%d\n", strlen(&p + 1));//随机值,同上理
printf("%d\n", strlen(&p[0] + 1));//5
printf("%d\n", sizeof(p));//4或8,p是地址
printf("%d\n", sizeof(p + 1));//4或8,p+1就是b的地址,还是地址
printf("%d\n", sizeof(*p));//1,拿出来a,大小就是1个字节
printf("%d\n", sizeof(p[0]));//1,将字符串当作数组来访问,p[0] = *(p+0),所以还是找到a
printf("%d\n", sizeof(&p));//4或8,取得还是地址
printf("%d\n", sizeof(&p + 1));//4或8,取的是跳出整个p的地址,但还是地址
printf("%d\n", sizeof(&p[0] + 1));//4或8,取出的是第二个元素b的地址
&p就是取指针变量p的地址
&p就是取指针变量p的首地址,而p里面存的只有a的地址,没有\0,所以随机值
二维数组
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));//3*4*4 = 48....3*4*sizeof(int)
printf("%d\n", sizeof(a[0][0]));//4,第一行第一个元素
printf("%d\n", sizeof(a[0]));//16
printf("%d\n", sizeof(a[0] + 1));//4或8,解释:a[0]作为数组名,并没有单独放在sizeof内部,
//也没有取地址&,而是与1结合,
//所以a[0]就代表第一行第一个的地址,+1就是第二个元素的地址
printf("%d\n", sizeof(*(a[0] + 1)));//4,解释:对(a[0]+1)第一行第二个元素的地址解引用就是第二个元素,是int类型
printf("%d\n", sizeof(a + 1));//4或8,解释:a并没有单独放在sizeof内部,因此就作为二维数组首元素的地址,
//二维数组首元素就是第一行,+1就是第二行的地址
printf("%d\n", sizeof(*(a + 1)));//16,解释:a+1是第二行的地址,所以解引用是第二行,*(a+1)<-->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没有单独放在sizeof中,所以a表示首元素的地址,即第一行的地址,解引用就是第一行
printf("%d\n", sizeof(a[3]));//16,a[3]在数组里不存在,a[3]其实是第四行数组名(如果有的话)
//所以其实不存在,也能通过类型计算大小的
补充:sizeof的用法
int main()
{
short s = 5;
int a = 4;
printf("%d\n", sizeof(s = a + 6));//2
printf("%d\n", s);//5
return 0;
}
由于sizeof内部不进行计算,因此只看最后输出为什么类型即可,最后输出为short类型,即2字节大小
因为sizeof内部不进行计算,所以s没有变化,所以s还是为5,打印即为5
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));//2 5
return 0;
}
由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}
*p假设p 的值为0x100000。 如下表表达式的值分别为多少?
已知,结构体Test类型的变量大小是20个字节
int main()
{
printf("%p\n", p + 0x1);//0x100014
printf("%p\n", (unsigned long)p + 0x1);//0x100001
printf("%p\n", (unsigned int*)p + 0x1);//0x100004
return 0;
}
int main()
{
int a[4] = { 1, 2, 3, 4 };
int* ptr1 = (int*)(&a + 1);
int* ptr2 = (int*)((int)a + 1);
printf("%x,%x", ptr1[-1], *ptr2);//4 2000000
//*(ptr+(-1)) --> *(ptr-1)
return 0;
}
/int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };//用了小括号括起来的,就是逗号表达式,只取最后
// 1 3 5
int* p;
p = a[0];
printf("%d", p[0]);//1
return 0;
}
int main()
{
int a[5][5];
int(*p)[4];
p = a;//int(*)[5]
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);//FFFFFFFC -4
return 0;
}
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int* ptr1 = (int*)(&aa + 1);
int* ptr2 = (int*)(*(aa + 1));
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//10 5
return 0;
}
int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);//at
return 0;
}
int main()
{
char* c[] = { "ENTER","NEW","POINT","FIRST" };
char** cp[] = { c + 3,c + 2,c + 1,c };
char*** cpp = cp;
printf("%s\n", **++cpp);//POINT
printf("%s\n", *-- * ++cpp + 3);//ER
printf("%s\n", *cpp[-2] + 3);//ST
printf("%s\n", cpp[-1][-1] + 1);//EW
return 0;
}
-------------------------------------------开始状态-------------------------------------------
-------------------------------------------**++cpp-------------------------------------------
-------------------------------------------*- -*++cpp+3-------------------------------------------
-------------------------------------------*cpp[-2] + 3-------------------------------------------
-------------------------------------------cpp[-1][-1] + 1-------------------------------------------
若是本文有出处,请各位小伙伴们留言哦~,看到会及时回复,另外制作不易,一键三连!!!