在指针初体验(C语言–指针初体验)中我们简单了解了指针的知识和使用:
那么从本文开始将记录在深入学习指针过程中的一些课件摘录和学习笔记。
字符型指针就是指向字符或者字符串的char*型指针,一般的使用如下:
int main()
{
char ch = 'w';//定义一个字符变量
char *pc = &ch;//将字符变量取地址存入一个字符型指针
return 0;
}
再比如:
int main()
{
const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗?
printf("%s\n", pstr);
return 0;
}
需要注意的是,这里并不是将整个“hello bit.”字符串存入pstr指针,而只是将字符串的首字母“h”的地址存入指针。这和字符数组常量赋值时是不一样的。
请看下面代码,思考输出结果:
#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两个字符指针却是相同的。
原因是定义初始化两个字符数组时,虽然内容相同,但却是在内存中开辟出两个不同的空间来存放相同的东西,那这两个空间的地址自然是不同的,str1和str2作为字符数组首字符地址也自然不同;
而str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。因此str3和str4是相同的。
此处需要注意的是,常量字符串是不能被修改的。如下代码其实是错误的:
char *ch = "world";
*ch = 'w';//常量字符串不可被修改
printf("%c\n", *ch);
因此,既然常量字符串无法被修改,那么两个指向相同字符串地址的指针变量也没必要开辟两个空间,共用一个就可以了。
详见:
https://www.runoob.com/w3cnote/cpp-const-keyword.html
指针数组本质上是一个数组,一个用来存放指针的数组。
即数组中存放的全是地址。
如下:
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组
举例:a,b,c三个都是数组名,所以可以存入指针数组中。
//指针数组:
int main()
{
int a[5] = { 1,2,3,4,5 };
int b[] = { 6,7,8,9,10 };
int c[] = { 1,2,3,4,5 };
int *arr[3] = { a,b,c };//指针数组
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
数组指针,顾名思义,其实是一种指针。
整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。
int a = 0;
int *pa = &a;
char b = 'h';
char *pb = &b;
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int (*parr)[10] = &arr;//数组指针
举一反三:
double *d[5];//指针数组
double * (*pd)[5] = &d; //数组指针
解释:parr先和*结合,说明parr是一个指针变量,然后指针指向的是一个大小为10个整型的数组。所以parr是一个指针,指向一个数组,叫数组指针。
这里要注意:[]的优先级要高于*号的,所以必须加上()来保证parr先和*结合。
数组名是数组首元素的地址,&数组名则表示取数组的地址,但是数组的地址也就是数组首元素的地址,所以从地址来看,一个数组arr[10]中arr和&arr指向的地址是相同的。但是它们的意义并不同!
阅读下面代码并观察输出结果:
void main()
{
int arr[10] = { 0 };
int *p1 = arr;
int(*p2)[10] = &arr;
printf("数组名:%p\n", arr);
printf("&数组名:%p\n", &arr);
printf("p1:%p\n", p1);
printf("p1+1:%p\n", p1 + 1);
printf("p2:%p\n", p2);
printf("p2+1:%p\n", p2 + 1);
}
输出结果:
可以发现,数组名、&数组名、p1、p2的内容都是相同的。
但是,与之前我们说过的指针类型中一样,虽然p1、p2的内容都一样,但它们的意义不同。
p1只代表数组首元素的地址,p1+1表示跳过了数组首元素,指向了下一个元素的地址。
p2代表的是整个数组的地址,p2+1则表示跳过了整个数组,而代码中的数组是int型且有10个元素,故而地址加了40个字节。
此处再次重申:
数组名表示数组首元素的地址,但是有2个例外:
1.sizeof(数组名)—数组名表示整个数组,计算的是整个数组的大小;
2.&数组名—数组名表示整个数组,取出的是整个数组的地址。
除此两种情况,其他均表示数组首元素地址!
再注:二维数组的数组名也表示首元素地址,但是二维数组的首元素是第一行!
#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);
//数组名arr,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
print_arr2(arr, 3, 5);
return 0;
}
下面代码各有各的意思:
int arr[5]; —整型数组
int *parr1[10];—指针数组,该数组存放10个元素均为整型指针
int (*parr2)[10];—数组指针:该指针指向一个数组,数组内存放了10个整形元素
int (*parr3[10])[5];—存放数组指针的数组,该数组存放了10个数组指针,每个数组指针指向一个数组,数组内5个元素均为整型。
对于最后一个可以这么想:我们知道例如int arr[10]
因为arr先和[]结合,所以这是一个数组,去掉arr[10]后,剩下的就是这个数组的类型。
那么最后一个也是如此,因为parr3先和[]结合,所以是一个数组,挖掉数组名和方块(parr3[10])剩下的如下图:
观察挖掉后的情形,可以看到其实是一个数组指针,所以parr3[10]是一个存放了10个数组指针的数组,而每一个数组指针又指向一个装了5个int元素的数组。
void test(int arr[])//没问题
{}
void test(int arr[10])//没问题
{}
void test(int *arr)//传过来的arr就是数组首元素地址,所以用整型指针接收没问题
{}
void test2(int *arr[20])//没问题
{}
void test2(int **arr)//传过来的arr2是数组首元素地址,所以用*arr来接受,同时arr2[20]的类型是int *,所以此处完整的用int **arr没问题!
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}
void test(int arr[3][5])//ok?没问题!
{}
void test(int arr[][])//ok?错误!
{}
void test(int arr[][5])//ok?没问题!
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字,即行可以省略,列必须保留!
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//ok?错误!传过来第一行地址,这个接收不了
{}
void test(int* arr[5])//ok?错误!这是一个指针数组,传过来的是地址,肯定要指针来接收,数组怎么接受?不匹配
{}
void test(int (*arr)[5])//ok?没问题!数组指针,这个指针指向有5个元素的数组。传过来的第一行就是有5个元素,且是一个地址,所以完全没问题!
{}
void test(int **arr)//ok?错误!
{}
int main()
{
int arr[3][5] = {0};
test(arr);//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;
}
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
例如有这样一个函数:
void test(char* p)
{}
那么调用函数时应该如何传参呢?
void main()
{
char ch = 'w';
test(&ch);//没问题!
char* p = &ch;
test(p);//同样没问题!
}
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
#include
void test(int** ptr)//参数部分是二级指针
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int*p = &n;//p是一级指针
int **pp = &p;//pp是二级指针
test(pp);//传二级指针,当然没问题
test(&p);//传一级指针的地址,相当于是二级指针,也没问题
int* arr[10] = {0};
test(arr);//传存放一级指针的数组的数组名也没问题,因为arr为数组首元素地址,数组内首元素是一个一级指针,所以arr也相当于是二级指针了
return 0;
}
数组:
1.一维数组: int arr[10]
2.二维数组: int arr[5][6]
3.指针数组: int* arr[10]–存放指针的数组–里面的元素都是指针指针:
1.一级指针:int *p
2.二级指针:int ** p
3.数组指针:int (*p)[10]–指向数组的指针