一、字符指针
重点:区别字符指针和字符数组的本质
#include<stdio.h>
#include<stdlib.h>
int main()
{
char str1[] = "hello";
char str2[] = "hello";
char*p1 = "hello";
char*p2 = "hello";
printf("str1=%p\n", str1);
printf("str2=%p\n", str2);
printf("p1=%p\n", p1);
printf("p2=%p\n", p2);
printf("st1大小:%d\n", sizeof(str1));
printf("p1大小:%d\n", sizeof(p1));
system("pause");
return 0;
}
先上运行结果:
(1)定义了str1和str2两个字符数组,存储的字符串相同。可以看到,输出的str1和str2地址不同。
(2)定义了p1和p2两个字符指针,存储的字符串相同。可以看到,输出的p1和p2地址相同。
为什么会出现这种情况?这里就涉及到字符串的不同定义方式下,存储位置的不同,图解如下
在栈上定义变量时,栈是向下增长的,所以先定义的临时变量地址大,后定义的小(这里可以观察str1h和str2的地址)。对于数组来说,它也在栈上开辟,但是很特别的是:数据开辟空间是整体开辟的,然后把第一个元素的地址作为数组的地址。数组中存储元素的地址是从左到右逐渐增大。str1和str2是字符数组,在栈上开辟空间,我们知道数组开辟空间是一整块的,所以会在栈上开辟一整块的空间来存储字符串
在main函数中定义char*p1="hello",其实是定义了两种变量-->(1)p变量,p是一个字符指针(2)“hello"是在字符常量区开辟空间的。字符常量区的字符是“只读”的,不能被修改。这里char*p1="hello";char*p2="hello",两个hello相同,,所以编译器就认为是同一个字符串,所以p1和p2变量中保存的都是hello的首字符地址,从而输出结果相同
而char str1[]="hello",是在栈上保存的,因为str1是一个数组,在栈上开辟空间,并且,开辟的是一整块空间,用来存储hello。同理,char str2[]="hello"也是在栈上开辟了一整块空间存储hello,str1与str2是两个不同的变量,地址显然不会相同
最后,sizeof(str1)=6,sizeof(p1)=4,也证实了我们的分析:str1开辟了6个字节的空间,存储了hello,而p1是指针的大小,p1中存储的是h的地址。
有了上面的分析,来看这道题-->
#include<stdio.h>
#include<stdlib.h>
int main()
{
char str1[] = "hello";
char str2[] = "hello";
char*p1 = "hello";
char*p2 = "hello";
if (str1 == str2)
{
printf("aaaaa\n");
}
else
{
printf("bbbbb\n");
}
if (p1 == p2)
{
printf("ccccc\n");
}
else
{
printf("ddddd\n");
}
system("pause");
return 0;
}
运行结果:
二、指针数组,数组指针
开篇重点,筋脉。优先级:()> [ ]>*
指针数组
举例:int*arr[10]
首先:因为优先级【】>*,所以arr先和【】结合,也就决定了它的本质是数组;数组中放的元素是指针,所以它是一个指针数组
举例:char*arr[10] 字符型指针数组
char**arr[10] 二级字符型指针数组
总结:
step1:先看本质是什么?(优先级决定)
step2:再看数组中放的是什么?放的是整型就是整型数组,放的是指针就是指针数组
三、&arr与arr
#include<stdio.h>
#include<stdlib.h>
int main()
{
int a = 6;
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);
system("pause");
return 0;
}
运行结果:
可以看到,虽然arr与&arr在数值上相同,但是通过+1输出的结果,我们可以看出它俩并不是一个概念。arr是数组名,代表数组首元素地址,但是要注意,这里所说的“元素”并不一定是一个原生的类型(如int,float,char),“元素”指的是数组中放的是什么。我们把所有的数组全部都当成一维数组,一维数组中放一维数组就是二维数组,这个“一维数组里面的一维数组”它的元素是什么?就是数组。所以,这里的“元素”要格外注意。
在大部分情况下,数组名代表数组首元素的地址(尤其是数组名参与运算时,比如arr+1,arr+2);只有在以下两种情况下,数据名代表整个数组
首元素的地址是元素的地址,数组的地址是数组的地址。它俩不一样(如何证明?arr+1和&arr+1)
(1)sizeof(arr)
(2)&arr
arr+1 指向的是下一个元素
&arr+1指向的是下一个数组
综上:&arr相当于一个数组指针
再看一道例子,深刻理解类型
int(*p)[10];
int a[10];
p = a;
可以看到,有警告,说明类型不同
int(*p)[arr]的类型是数组指针,arr的类型是元素指针(整型指针),所以类型不一样
改为:p=&a则编译通过,说明两个类型一样了。&a是数组的地址,地址本质也是指针,所以就是数组的指针。这样一来,类型就一样了。
如果把数组指针的类型改为以下:
char(*p)[10];
int a[10];
p = &a;
可以看到,出现警告。
数组也有类型,数组指针同理。一个是整型数组指针,一个是char类型数组指针,类型当然不同。所以在定义类型时,必须一样
如果把数组大小改为以下:
int(*p)[9];
int a[10];
p = &a;
综上两个例子,也说明了数组的类型由原生类型+数组的大小共同决定。所以以后在定义数组和定义指针时,数组的大小也是作为数组类型的一部分,一定要注意。
四、二维数组的传参
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
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);
system("pause");
return 0;
}
观察这两种传参方式
数组在传参时会降维成指向其内部元素类型的指针,这个指针叫做数组指针。
arr[3][5]内部元素是一维数组。我们可以把arr[3][5]看做是有三个元素的一维数组,其中每一个元素又是一个一维数组,它有5个元素。所以里面放的是什么?一维数组;什么一维数组?整型一维数组。所以它里面放的是具有5个元素的整型一维数组。arr[3][5]发生降维是降维成了整型数组指针。
我们可以这样认为。arr[3][5]是一个一维数组,里面的元素是arr[5],在传参时会发生降维,降维成指向其内部元素类型的指针,而内部元素是一个一位数组,所以,降维成了数组指针。
下面一段代码就验证了二维数组在传参时会降维成指针-->
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
void print1(int a[3][5], int r, int c)
{
printf("%d\n", sizeof(a));
}
void print2(int (*p)[5], int r, int c)
{
printf("%d\n", sizeof(p));
}
int main()
{
int a[3][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
print1(a, 3, 5);
print2(a, 3, 5);
system("pause");
return 0;
}
运行结果:
问题二:上面的代码二维数组传参时为什么3可以被忽略,而5不行?
(1)忽略3
void print1(int a[][5], int r, int c)
{
printf("%d\n", sizeof(a));
}
(2) 忽略5
void print1(int a[3][], int r, int c)
{
printf("%d\n", sizeof(a));
}
a[3][]其实是一个指针,准确来说是数组指针。对于数组而言,数组的维度也是它类型的一部分。也就是说,如果把第二个数组当中的数字忽略了,那么这个数组指针当中的数组类型其实就不完整了,数组的概念都不完全了,数组指针的类型当然也不明确了。
如果改为-->
void print1(int a[][6], int r, int c)
{
printf("%d\n", sizeof(a));
}
元素当中的数据也是类型的一部分,所以它的大小也必须一致。
对于一维数组传参,维度其实无所谓,可有可无(对于如下代码,都可以正常编译通过)
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
void fun(int a[200], int num)
{}
int main()
{
int b[10];
fun(b, 10);
system("pause");
return 0;
}
五、数组参数和指针参数
(1)一维数组传参
void test(int arr[])//正确
{}
void test(int arr[10])//正确
{}
void test(int*arr)//正确
{}
void test2(int*arr[10])//正确,类型一致,都是指针数组,作为参数传递时会降维成int**
{
printf("%d", sizeof(arr));
}
void test2(int**arr)
{}
int main()
{
int arr[10] = { 0 };
int*arr2[20] = { 0 };//指针数组
test(arr);
test2(arr2);
system("pause");
return 0;
}
(2)二维数组传参
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
void test(int arr[][])//错误,只能忽略第一个维度“int (*)[1]”和“int [3][5]”数组的下标不同
{}
void test(int arr[][5])//正确
{}
void test(int*arr)//错误,这里是一个整型指针。“函数”:“int *”与“int [3][5]”的间接级别不同
{}
void test(int* arr[5])//错误,它是指针数组,在传参时会降维成int**。“函数”:“int **”与“int [3][5]”的间接级别不同
{}
void test(int(*arr)[5])//正确,数组指针
{}
void test(int**arr)//错误,它是指针的指针。“int **”与“int [3][5]”的间接级别不同
{}
int main()
{
int arr[3][5] = { 0 };//二维数组,在传参时会降维成数组指针,类型为int*arr[5]
test(arr);
system("pause");
return 0;
}
(3)一级指针传参
当函数的参数部分为一级指针时,函数能够接收的参数-->
void test1(int*p){ }
可以接收的参数:整型指针
整型变量的地址
整型数组
void test2(char*p){ }
可以接收的参数:字符指针
字符型变量的地址
字符数组
(4)二级指针传参
#include<stdio.h>
#include<stdlib.h>
void test(int** ptr)
{
printf("num=%d\n", **ptr);
}
int main()
{
int n = 10;
int *p = &n;
int**pp = &p;
test(pp);//传int**变量
test(&p);//传int*p,把&p传进去
system("pause");
return 0;
}