文章目录
- 一.数组与指针
- 1. 数组名的理解
- 2. 使用指针访问数组
- 3. 一维数组传参的本质
- 4. 冒泡排序
- 5. 二级指针
- 6. 指针数组
- 7. 指针数组模拟二维数组
- 8. 字符指针变量
- 9. 数组指针变量
- 10. 二维数组传参的本质(数组指针的应用场景)
- 二.函数与指针
- 1.函数指针变量
- 2.typedef关键字
- 3. 函数指针数组
- 1.是什么?
- 2.转移表:函数指针数组的用途
- 三.注意的区别
- 1.数组指针与指针数组
- 2.函数指针与函数指针数组
一.数组与指针
1. 数组名的理解
1.在使用指针访问数组的内容时,有这样的代码:
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
这里我们使用 &arr[0] 的方式拿到了数组第一个元素的地址,但是其实数组名本来就是地址,而且是数组首元素的地址,我们来做个测试:
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("&arr[0] = %p\n", &arr[0]);
printf("arr = %p\n", arr);
return 0;
}
输出结果:
我们发现数组名和数组首元素的地址打印出的结果一模一样,数组名就是数组首元素(第一个元素)的地址。
2.但有两个例外:
-
sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
-
&数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)
代码表现:
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
&arr[0],arr,&arr 的区别
&arr[0]=arr!=&arr
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("&arr[0] =%p\n", &arr[0]);
printf("arr =%p\n", arr);
printf("&arr =%p\n", &arr);
//都为00EFFD80
//因为数组名就是数组首元素的地址,所以&arr[0]=arr;
//&arr 表示数组的地址,但以第一个为表现形式
printf("\n");
printf("&arr[0]+1 =%p\n", &arr);//四个字节
printf("arr+1 =%p\n", arr);
printf("&arr+1 =%p\n", &arr+1);//四十个字节
//因为&arr,表示整个数组,所以加一,表示跳过十个元素,为四十个字节。
return 0;
}
return 0;
}
输出结果:
这里我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1 相差4个字节,是因为&arr[0] 和 arr 都是首元素的地址,+1就是跳过一个元素。
但是&arr 和 &arr+1相差40个字节,这就是因为&arr是数组的地址,+1 操作是跳过整个数组的。
2. 使用指针访问数组
可以有两种方法,下标法与指针法。
1.使用下标的方式访问数组
1.使用下标的方式访问数组
int main()
{
int arr[10] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
{
scanf("%d", &arr[i]);
}
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
这就是一般数组的输入与输出的基本框架。
2.使用指针访问数组
2.使用指针访问数组
int main()
{
int arr[10] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
int* p = arr;
for (i = 0; i < sz; i++)
{
scanf("%d", p + i);//(p=&arr[0])+i=&arr[0+i]
scanf("%d", arr + i);//arr=&arr[0]=p
}
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
printf("%d ", *(arr + i));
}
return 0;
}
3. 一维数组传参的本质
数组我们学过了,之前也讲了,数组是可以传递给函数的,这个小节我们讨论一下数组传参的本质。
首先从一个问题开始,我们之前都是在函数外部计算数组的元素个数,那我们可以把数组传给一个函数后,函数内部求数组的元素个数吗?
void test(int* arr)
{
int sz2 = sizeof(arr) / sizeof(arr[0]);
printf("sz2=%d\n", sz2);//1
//因为数组的函数传参只传一个元素
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz1 = sizeof(arr) / sizeof(arr[0]);
printf("sz1=%d\n", sz1);//10
test(arr);
return 0;
可以见数组所有的参数,没有进入函数内,因为数组名是数组首元素的地址;那么在数组传参的时候,传递的是数组名,也就是说本质上数组传参传递的是数组首元素的地址。
所以函数形参的部分理论上应该使用指针变量来接收首元素的地址。那么在函数内部我们写sizeof(arr) 计算的是一个地址的大小(单位字节)而不是数组的大小(单位字节)。正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。
修改为:
void test(int* arr, int sz)//int arr[]也可以
//⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d", arr[i]);
printf("%d", *(arr + i));
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz1 = sizeof(arr) / sizeof(arr[0]);
printf("sz1=%d\n", sz1);//10
test(arr, sz1);
return 0;
}
这样,函数就可以调用数组的地址与大小,从而打印出来数组。
4. 冒泡排序
定义:两两相邻的元素作比较,不满足就交换,满足顺序就找下一个(升序或降序)
即:
如图示,9与8相比,9大,所以9往右一个单位,继续9与7进行比大小,依次进行下去,直到排列整齐。
//输入函数
void input(int* arr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
scanf("%d", arr + i);
}
}
int count = 0;
void bubble_sort(int* arr, int sz)
{
//确定趟数
int i = 0;
for (i = 0; i < sz - 1; i++)
//10个元素,要进行9次。
{
int flag = 1;//假设已经满足顺序
//每一趟内部的比较
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
count++;
if (arr[j] > arr[j + 1])
{
//数值代换
flag = 0;
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
if (flag == 1)
{
break;
}
}
}
void printf_arr(int* arr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
int arr[10] = { 0 };
//输入
int sz = sizeof(arr) / sizeof(arr[0]);
input(arr, sz);
//排序
bubble_sort(arr, sz);
//打印
printf_arr(arr, sz);
return 0
}
这个代码中,就(sz-1-i)比较难理解,其实很简单,我们把sz-1先看成一个整体,他表示要进行9次,然后再看(-i),i是for循环的初始元素,i开始为0。然后,我们可以看上面的图,第一趟我们是1-9个元素,完成比较,减了一个元素。第二趟我们就是2~8个元素,再减了一个元素。所以用i的依次增加,来代替。
5. 二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
这就是 二级指针 。
int main()
{
int a = 10;
int* pa = &a;//一级指针,pa为变量
int** paa = &pa;//二级指针,paa为变量
//*paa 一个* 为paa的地址为pa
//**paa 二个** 为解引用 即**paa==*pa=a=10;
printf("%d\n", **paa);
**paa = 200;
printf("%d\n", a);
return 0;
}
对于二级指针的运算有:
• *ppa 通过对ppa中的地址进行解引用,这样找到的是 pa
int b = 20;
*ppa = &b;//等价于 pa = &b;
• **ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a .
**ppa = 30;
//等价于*pa = 30;
//等价于a = 30
6. 指针数组
指针数组的每个元素都是用来存放地址(指针)的。
正如:整型数组,是存放整型的数组,字符数组是存放字符的数组。
存放指针的数组,数组的每个元素是指针
int main()
{
int a = 0;
int b = 0;
int c = 0;
int* arr[3] = { &a,&b,&c};
// 0 1 2
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%d ", *(arr[i]));
}
return 0;
}
7. 指针数组模拟二维数组
int main()
{
int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 1,2,3,4,5 };
int arr3[5] = { 1,2,3,4,5 };
int* arr[3] = { arr1,arr2,arr3 };
// 0 1 2
int i = 0;
for (i = 0; i < 3; i++)//行
{
int j = 0;
for (j = 0; j < 5; j++)//列
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型一维数组,parr[i][j]就是整型一维数组中的元素。
上述的代码模拟出二维数组的效果,实际上并非完全是二维数组,因为每一行并非是连续的。
8. 字符指针变量
在指针的类型中我们知道有一种指针类型为字符指针 char*
一般使用:
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
还有⼀种使用方式如下:
int main()
{
const char* p = "abcdef";
printf("%c\n", *p);//首个元素地址
printf("%s\n", p);//使用%s打印字符串的时候,只需要提供首字符的地址就行
*p = 'q';//字符指针无法改数值
return 0;
}
且在《剑指offer》中也收录了一道和字符串相关的笔试题
#include <stdio.h>
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;
}
这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区内。
当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。
9. 数组指针变量
1.是什么?
• 整形指针变量: int * pint; 存放的是整形变量的地址,能够指向整形数据的指针。
• 浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。
那数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量。
数组指针变量
int (*p)[10];
解释:p先和星号结合,说明p是一个指针变量,然后指针指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫 数组指针。
这里要注意:[]的优先级要高于星号的,所以必须加上()来保证p先和*结合。
2.初始化
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int (* p)[10] = &arr;//取出的是数组的地址
//想使用p这个数组指针访问arr数组的内容
for (i = 0; i < 10; i++)
{
printf("%d ",(* p)[i]);//比较复杂,没必要
}
//或
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", p[i]);//p[i] --- *(p+i)
}
return 0;
}
10. 二维数组传参的本质(数组指针的应用场景)
void print(int arr[3][5], int r, int c)
{
int i = 0;
for (i = 0; i < r; i++)
{
int j = 0;
for (j = 0; j < c; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
//⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式
void print(int(*arr)[5], int r, int c)
{
int i = 0;
for (i = 0; i < r; i++)
{
int j = 0;
for (j = 0; j < c; j++)
{
printf("%d ", *(*(arr + i) + j));//*(arr+i)==arr[i]
}
}
}
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数组的内容打印出来
return 0;
}
根据数组名是数组首元素的地址这个规则,二维数组的数组名表示的就是第一行的地址,是一维数组的地址。根据上面的例子,第一行的一维数组的类型就是 int [5] ,所以第一行的地址的类型就是数组指针类型 int(*)[5] 。那就意味着二维数组传参本质上也是传递了地址,传递的是第一行这个一维数组的地址,那么形参也是可以写成指针形式的。
二.函数与指针
1.函数指针变量
1.函数指针变量是用来存放函数地址的,未来通过地址能够调用函数的。
void Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 10;
int* pa = &a;//整型指针变量
int arr[10] = { 0 };
int(*parr)[5] = &arr;//数组指针变量
//arr &arr 没有区别
printf("%d\n", arr);
printf("%d", &arr);
int(*pf)(int, int) = &Add;//函数可以没有&
//pf是函数指针变量
int ret2 = (*pf)(4, 5);
printf("%d ", ret2);
int(*)(int, int)函数指针类型
return 0;
}
2.使用:
#include <stdio.h>
int Add(int x, int y)
{
return x+y;
}
int main()
{
int(*pf3)(int, int) = Add;
printf("%d\n", (*pf3)(2, 3));
printf("%d\n", pf3(3, 5));//可以不加解引用
return 0;
}
输出结果:
- 5
- 8
2.typedef关键字
typedef 是用来类型重命名的,可以将复杂的类型,简单化。
typedef unsigned int unit;
int main()
{
unsigned int num;
unit num;
return 0;
}
typedef int* pint_t;
int main()
{
int* p;
pint_t p2;
return 0;
}
//但是对于数组指针和函数指针稍微有点区别:
typedef int(*parr_t)[6];//名称写入括号内
int main()
{
int arr[6] = { 0 };
int(*p)[6] = &arr;
parr_t p2 = &arr;
return 0;
}
3. 函数指针数组
1.是什么?
把函数的地址存到一个数组中,那这个数组就叫函数指针数组.
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;
}
int main()
{
int* arr1[6];//整形指针数组
char* arr2[5];//字符指针数组
//arr3[5];每个元素是函数的地址—函数指针数组
int (*pf1)(int, int) = Add;
int (*pf2)(int, int) = Sub;
int (*pf3)(int, int) = Mul;
int (*pf4)(int, int) = Div;
//函数指针数组,来存放这些函数的地址
// 数组的内容是 int (*)() 类型的函数指针
int (*pf[4])(int, int) = { Add,Sub,Mul,Div };//*pf[]表示数组,4表示有四个函数指针
// 0 1 2 3
int i = 0;
for (i = 0; i < 4; i++)
{
int ret = pf[i](6, 2);
printf("%d\n", ret);
}
return 0;
}
2.转移表:函数指针数组的用途
举例:计算器的一般实现:
void menu()
{
printf("*************************\n");
printf("*******1.Add 2.Sub ***\n");
printf("*******3.mul 4.div ***\n");
printf("*******0.exit ***\n");
printf("*************************\n");
}
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;
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
int (*prarr[5])(int, int) = { 0,Add,Sub,Mul,Div};
0 1 2 3 4
do
{
menu();
printf("请选择:");
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("请输入二个操作数:");
scanf("%d%d", &x, &y);
ret = prarr[input](x, y);
printf("%d\n", ret);
}
else if (input == 0)
{
printf("退出计算器\n");
}
else
{
printf("选择错误,重新选择\n");
}
} while (input);
return 0;
}
三.注意的区别
1.数组指针与指针数组
int main()
{
//数组指针
int (*p) [2]=&arr;
printf("%d ", *(*(arr + i) + j))//二维数组传参
int *p=arr;
printf("%d ", p[i]);//p[i] --- *(p+i)
//p先和*号结合,说明p是⼀个指针变量,然后指针指向的是⼀个⼤⼩为10个整型的数组。
//所以p是⼀个指针,指向⼀个数组,叫数组指针。
//指针数组
int arr[]={1,2,3,4};
int arr2[]={2,3,4,5};
int *arr[3]={arr1,arr2};
printf("%d ", *(arr[i]));//要解引用
//指针数组的每个元素都是⽤来存放地址(指针)的。
//*先于数组结合,表示它为数组
//且后面大括号内加的是变量的地址,所以是指针
int a=0;
int b=0;
int *arr[2]={&a,&b};
return 0;
}
2.函数指针与函数指针数组
int main()
{
//函数指针
int(*pf3)(int, int) = Add;//可以不加&
printf("%d\n", (*pf3)(2, 3));
printf("%d\n", pf3(3, 5));//可以不用解引用
//函数指针数组
int (*pf[4])(int, int) = { Add,Sub,Mul,Div };//*pf[]表示数组,4表示有四个函数指针
ret = pf[input](x, y);
printf("%d\n", ret);
return 0;
}
这就是指针大部分知识点,但后面还有少部分,与数组的面试题,敬请期待,欢迎大佬们的建议与指导!