目录
前言
函数?
嗯,我懂。
数组?
嗯,我懂。
指针?
嗯,我也懂。
字符指针?整形指针?数组指针?指针数组?函数指针?函数指针数组?指向函数指针数组的指针?
。。。。。。
我知道你很急,但你先别急,这期,咋们来一一探讨 。
字符指针
什么是字符指针,简单来说,字符指针仍然是一个指针变量,只不过指针变量指向的变量的类型是字符型。
int main() {
int a = 0;
int* p = &a; //p是一个整形指针,指向的变量的类型是整型
char b = 'A';
char* p1 = &b; //p1是一个字符指针,指向的变量是字符型
}
上面是字符指针最常用的一种应用,下面这种是不经常实用的:
int main() {
char ch[10] = "helloworld";
char* p3 = "helloworld";
printf("%s\n",p3);
printf("%c\n",*p3);
return 0;
}
暂且不讨论运行结果,有些人认为指针变量p3指向了后面的整个字符串,实质上是这样吗?实际上并不是这样,p3是一个字符指针,我们规定p3指向了后面的字符串的第一个元素'h'。
通过运行结果不难发现,p3确实指向了字符串的第一个元素。
这里需要注意:后面的字符串"helloworld"是一个常量字符串,他在内存中只开辟了一块空间,也就意味这不管创建了多少个指针变量,且这些指针变量都指只向了"helloworld"的第一个元素'h'所在的同一块空间。
指针数组和数组指针
指针数组
指针数组我们初阶已经讲过,简单回顾一下:
指针数组,指针可以理解为定语,数组可以理解为主语,所以指针数组本质上就是一个数组,只不过指针数组的每个元素就是一个指针变量。
int main() {
int* arr1[10]; //整形指针的数组
char* arr2[4]; //一级字符指针的数组
char** arr3[5];//二级字符指针的数组
return 0;
}
数组指针
数组指针,数组是定语,指针是主语,所以数组指针本质上就是一个指针变量。我们之前学过整型指针,字符指针,整形指针存放的是整型变量的地址,字符指针存放的是字符变量的地址,同理,数组指针存放的就是数组的地址。但我们需要注意数组指针的写法:
int main() {
int arr[10] = { 1,2,3,4,5,6,7,8 };
int(*p)[10] = &arr; //数组指针
return 0;
我们这里需要区分一下数组指针和指针数组的写法:
int* arr1[10];
因为[]的优先级高于*,所以arr1先和[]结合,表明arr1本质上是一个数组,然后又跟*结合,这就表明,上述代码表明arr1是一个指针数组。
int(*p)[10] = &arr;
p先于*结合,表明p是一个指针变量,然后又与[]结合,表明p指向了一个长度是10的数组,且数组的每个元素的类型都是int型
数组指针的应用
我们之前学过了二维数组的遍历,下面我们用数组指针的方式进行二维数组的遍历。
void print_arr1(int arr[3][5], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
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++)
{
int j = 0;
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;
}
运行结果:
解析:在数组那期我们讲过,数组名表示数组首元素的地址,但是有两种情况除外,sizeof(数组名)和&数组名,这两种情况下,数组名都表示整个数组。
int main(){
int arr[10]={1,2,3,4,5};
arr; //表示数组首元素的地址
&arr[0]; //表示数组首元素的地址
&arr; //表示整个数组的地址
}
这三者的区别在数组那一期我们讲过,今天我们便不再过多阐述,现在我们要讨论得的是二维数组首元素的地址:
二维数组不像一维数组,以为数组首元素的地址是数组的第一个元素的地址,但是二维数组跟一维数组是有区别的,在二维数组中,我们规定,二维数组首元素的地址就是第一行数组的地址,即就是第一行数组的地址,切记,是第一行一维数组的地址,所以,二维数组的数组名就是它首元素的地址,即就是第一行的一维数组的地址,所以如果将它传给了形参,应该用数组指针去接收。这便是上述数组指针应用的解释。
数组参数
一维数组传参
什么是数组参数呢?
我们当时在函数调用时讲过,函数调用涉及到两个参数,形参和实参,数组参数也是一样的,此时的数组就是我们的实参,将数组传给形参之后,这个形参的类型应该是什么,这便是我们接下来要讨论得问题? 我们先分析下面这块代码:
void test(int arr[]) //1
{}
void test(int arr[10]) //2
{}
void test(int* arr) //3
{}
void test2(int* arr[20]) //4
{}
void test2(int** arr) //5
{}
int main()
{
int arr[10] = { 0 };
int* arr2[20] = { 0 };
test(arr);
test2(arr2);
return 0;
}
解析:对于1,2,3。main函数中我们调用test函数时,实参传的是arr,即数组名,即数组首元素的地址, 当我们理解为数组名时,可以理解为传过去了整个数组,所以test函数中的形参,我们可以定义两个数组去接收,当我们理解为数组首元素的地址时,因为数组的首元素是int类型,所以test函数中的形参我们应该用int*类型的指针去接收。
对于4,5,arr2是一个指针数组,main函数中我们调用test2函数时,实参是arr2,即数组名,即数组首元素的地址,当我们理解为数组名时,可以理解为传过去了整指针个数组,所以test2函数中的形参,我们可以定义一个指针数组去接收,当我们理解成数组首元素的地址时,因为数组的首元素是int*类型,所以形参我们应该用int**类型的指针去接收。
二维数组传参
分析下列代码:
void test(int arr[3][5]) //1
{}
void test(int arr[][]) //2
{}
void test(int arr[][5] )//3
{}
void test(int *arr) //4
{}
void test(int* arr[5]) //5
{}
void test(int (*arr)[5]) //6
{}
void test(int **arr) //7
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}
解析 :main函数中调用test函数时实参是arr可以理解为数组名,也可以理解为数组首元素的地址:
当实参arr被理解成数组名时,1,3可以作为形参去接收arr,但是2不行,因为二维数组的行可以省略,但是列不能省略。不清楚的小伙伴可以去数组那一期去回顾一下。
当实参arr被理解成数组首元素的地址时,上面我们刚刚分析过,二维数组首元素的地址其实就是数组第一行的一维数组的地址,即二维数组首元素的地址就是一个数组的地址,所以实参就是一个数组的地址,所以形参我们进行接收时,实参必须是一个数组指针去接收,所以只有6可以,4,5,7是不行的。
指针参数
指针参数其实就是当实参是指针时,形参该是什么类型。
一级指针传参
先看下列代码:
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]);
print(p, sz);
return 0;
}
解析: 指针传参其实相当于数组传参,其实更容易理解,上面的数组进行遍历时,实参传的是指针变量p,所以形参进行接收时,形参变量的类型自然可以使用与实参变量相同的int*类型去接收。
二级指针传参
先看下列代码:
void test(int **p) {
printf("%d\n",**p);
}
int main() {
int a = 10;
int* p = &a;
int** pp = &p;
test(pp);
test(&p);
return 0;
}
解析: main函数中的函数进行调用时,实参分别是二级指针和一级指针的地址,当实参为二级指针时,当然形参的类型可以是二级指针。当实参为一级指针的地址时,当然也可以用二级指针进行接收。
指针进阶到此还没结束,欲知后事如何,请看下回分解,如果一次讲完,就会:
好了本期内容到此结束!^_^