第一篇移步CSDNhttps://mp.csdn.net/mp_blog/creation/editor/139301675
第二篇移步CSDNhttps://mp.csdn.net/mp_blog/creation/editor/139329194
目录
ok,也是老样子六个标题用完了,写这个东西对我的知识梳理其实挺大的,希望也能帮助到你们,好像还有一点没有写完,哈哈
一、指针数组
指针数组是指针?还是数组呢?
char arr[10] 字符数组——存放字符的数组
int arr[5]整形数组——存放整型的数组
指针数组——存放指针的数组,数组的每个元素其实是指针类型
char *arr[5]存放字符指针的数组 数组中的每个元素是char*
int *arr[5]存放整型指针的数组 数组中的每个元素是int*
指针数组的每个元素是地址,又可以指向一块区域
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = 30;
//int* pa = &a;
//int* pb = &b;
//int* pc = &c;
int* arr[3] = { &a,&b,&c };
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%d ", *(arr[i]));
}
}
这个时候就能通过解引用操作符来将指针数组中的元素进行取出来。
接下来写一个指针数组来模拟二维数组
#include <stdio.h>
int main()
{
int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 2,3,4,5,6 };
int arr3[5] = { 3,4,5,6,7 };
int* arr[] = { arr1,arr2,arr3 };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
printf("%d ", arr[i][j]);
printf("\n");
}
return 0;
}
我们并没有创建一个二维数组,而是通过一个指针数组将它三个一维数组们串联起来
arr[i][j]等价于(*(arr+i)+j) ___*(arr+i)拿到的是一行的数组名,相当于arr[i]是首元素的地址+j就可以访问一行数组中的元素的地址,再进行解引用就能找出每个元素的取值。
二、字符指针变量
#include <stdio.h>
int main()
{
//char* p = "abcdef";
char arr[]= "abcdef";
char* p = arr;
return 0;
}
我们来看第一行的代码和下面两行的代码有什么区别
首先我们先来说下面两行的代码
1.下面是创建了一个数组。然后指针指向的是数组首元素的地址,其次数组的特点是连续且里面的字符串的内容是可以被修改的
2.第一行的代码是一个后面的内容是一个常量字符串,常量字符串的内容是连续不可以被修改的,但是这六个字节大小怎么存储到只有四个字节大小的指针呢,所以这里的指针指向的也是常量字符串首元素的地址,我们来验证一下这个内容。
这里补充一个知识点,使用%s进行打印字符串的时候只需要提供首元素的地址就行了,所以也不需要进行解引用进行操作
因为这里是常量字符串所以我们在进行以下操作的时候是做不到的
既然不期望被修改,我们就可以用到
#include <stdio.h>
int main()
{
const char* p = "abcdef";
printf("%c\n", *p);
printf("%s\n", p);
return 0;
}
加上一个const来对它进行一个约束,这是一个标准的写法,因为你不加const其实也不会报错但不会过去。
三、数组指针变量
指针数组——是数组,存放的是指针。
我们接着用类比法来推
字符指针——char*——指向字符的指针-字符指针变量中存放字符变量的地址
整型指针-int*——指向整型的指针——整形指针变量中存放整型变量的地址
数组指针——指向数组的指针——数组指针变量中存放数组的地址
什么是数组的地址,在前面我们说过其实是&数组名
int main()
{
int arr[10] = { 1,2,3,4,5 };
int(*p)[10] = &arr;//p就是数组指针,p中存放的是数组的地址
return 0;
}
int * ptr 我们来分析ptr是整型指针int *是他的类型
那么对于int(*p)[10]呢,去掉名字其实就是它的类型类型就是int(*)[10]
所以说&arr的类型也要跟等号的右边相符合就是int(*)[10]
arr——的类型是int * arr+1跳过四个字节
&arr[0]——的类型也是int * &arr[0]+1也是跳过四个字节
&arr——的类型是int(*)[10] &arr+1跳过的是40个字节,这也解答了我们前面留下的悬念,这主要由他的类型来决定。
这是一个指针,它指向的是一个大小为10个整形的数组。
这里要注意:[ ]的优先级要高于*号的,所以必须加上()来保证p先和*结合
那我们想使用p这个数组访问arr数组的内容
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int(*p)[10] = &arr;
for (int i = 0; i < 10; i++)
{
printf("%d ", (*p)[i]);
}
}
这里我们的(*p)[i]中的(*p)相当于拿到了一个数组,拿到了一个数组相当于拿到了一个数组名所以我们想要访问它的每个元素只需要给他加一个下标就完事了,当然这样写有点麻烦主要是为了体现数组指针变量的特点
int main()
{
//int a = 10;
//int* p = &a;
//*p;//*&a==a;
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int*p = arr;
for (int i = 0; i < 10; i++)
{
printf("%d ", p[i]); //p[i]--*(p+i)
}
}
由上面几行代码我们可以发现,其实p=&a;然后*p这样其实是一个*&可以相互抵消的操作
所以其实我们也不需要写的那么复杂平时,将数组名也就是数组首元素的地址交给指针p然后一个个去访问就行了
四、二维数组传参的本质
一维数组传参
数组名是首元素的地址
一维数组在传参的时候,其实传递的是首元素的地址。
函数的参数的可以写成数组,也可以写成指针
二维数组呢?
二维数组的数组名是如何理解的呢
其实二维数组的数组名也是数组首元素的地址
二维数组可以理解位一维数组的数组
二维数组的每一行可以看作是一个一维数组
所以二维数组其实是一个以为数组的数组
二维数组的首元素就是他的第一行
二维数组的数组名就是他第一行的地址
第一行是一个一维数组,传过去的就是第一行这个一维数组的地址
void print(int (*arr)[5], int r, int c)
{
for (int i = 0; i < r; i++)
{
for (int j = 0; j < 5; j++)
{
printf("%d ", ((*(arr+i))+j));
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { {1,2,3,4,5} ,{2,3,4,5,6}, {3,4,5,6,7} };
print(arr,3,5);//将arr数组的内容打印出来
//arr是数组首元素的地址,是他第一行的地址
return 0;
}
我们传过去的是数组首元素的地址,也就是第一行的地址,所以我们访问的时候用i来访问每一行的地址,用j来访问每一行的每一个元素的地址
在访问第一行的元素是就是arr[0][j]以此类推接下来的每一行的元素
arr[i]是第i行的数组名,数组名又表示数组首元素的地址,arr[i]表示是&arr[i][0]
五、函数指针变量
变量有地址
数组也有地址
函数是否也有地址
接下来让我们写几行来代码来验证一下
int Add(int x, int y)
{
return x + y;
}
int main()
{
printf("%p\n", &Add);
printf("%p\n", Add);
return 0;
}
这时候我们发现数组也是有地址的
我们写数组名和&数组名的时候发现两个地址是不一样的,但是我们对函数实行相同的操作的时候就会发现
&函数名和函数名都是函数的地址,没有区别
那我们怎么将它存储起来呢
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = &Add;
return 0;
}
int (*pf)(int, int) 首先它是一个指针,其次我们要将它和函数结合起来,函数的参数类型是(int,int),指向的函数的返回类型也是int所以就写成 int (*pf)(int, int)
同样的去掉名字也是函数指针类型int (*)(int, int)
那我们怎样去调用这个函数呢
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = &Add;
int ret = (*pf)(4, 5);
printf("%d\n", ret);
return 0;
}
*pf是对这个函数取出来使用,然后我们再把值传过去就得到了对这个函数进行使用
我们上文说到其实&函数名和函数名是等价的
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = Add;
int ret = pf(4, 5);
printf("%d\n", ret);
return 0;
}
那我们其实是把Add这个函数名传给函数指针变量pf,那我们其实也能使用pf去充当这个函数名 ,只不过*pf可以帮助我们更好的去理解
六、typedef关键字
typedef是用来类型重命名的,可以将复杂的类型,简单化。
比如,你觉得unsigned int写起来不方便,如果能写成uint就方便多了
typedef unsigned int uint;
int main()
{
unsigned int num;
uint num2;
return 0;
}
这样重命名就可以使用了
接下来我们对整型指针变量进行重命名试一下
typedef int* pint_t;
int main()
{
int* p;
pint_t p2;
return 0;
}
接下来我们试一下对数组指针变量进行重命名一下
typedef int(*parr_t)[6];
int main()
{
int arr[5] = { 0 };
int(*p)[6] = &arr;
parr_t p2 = &arr;
return 0;
}
这个有点特殊 int(*)[6] 是变量parr_t的类型,但是parr_t要放到括号里面这才代表
接下来函数指针变量
int Add(int x, int y)
{
return x + y;
}
typedef int(*pf_t)(int, int);
int main()
{
int(*pf)(int, int) = Add;
pf_t pf2 = Add;
return 0;
}
这个其实跟上面的大差不差