目录
一、字符指针
字符指针是指针,是指向字符的指针,字符指针的类型为char*。
例1:
char ch = 'w';
char* pc = &ch;//pc就是字符指针,存放ch的地址,也可以通过解引用改变ch的值
例2:
char* p = "abcdef";//p是字符指针,把首字符a的地址存放在p种
//其中,"abcdef"是常量字符串,存放在常量区,所以只能读取,不能修改
//其实质应该是
const char* p = "abcdef";//避免*p来修改其中的内容
*p = 'w';//err
值得注意的是,此处并不是把“abcdef”存放在p中,因为“abcdef”的大小是6个字节,地址的大小是4个字节。
经过上述理解,我们来看这道题目:
#include<stdio.h>
int main()
{
char str1[] = "hello world.";
char str2[] = "hello world.";
char * str3[] = "hello world.";
char * str4[] = "hello world.";
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");
if (&str3 == &str4)
printf("&str3 and &str4 are same\n");
else
printf("&str3 and &str4 are not same\n");
return 0;
}
运行结果如下:
究其原因:
- str1和str2是字符数组,分别在内存中创建了两个独立的空间,空间中都存放“hello world.”,所以地址是不一样的。
- str3和str4是字符指针,“hello world.”是常量字符串,字符指针指向常量字符串,常量字符串储存在只读区域,一旦创建好就不能被修改,那么程序就没有必要保存多份。所以,虽然分别创建了字符指针str3和str4,但都指向相同的地址。
二、指针数组
整形数组--存放整形的数组
字符数组--存放字符的数组
指针数组--存放指针的数组
指针数组是数组,存放在数组中的元素都是指针类型的。例如:int* arr[5];存放整型指针的数组。
用法:可以使用指针数组描述(模拟)一个二维数组。
#include<stdio.h>
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
int* arr[] = { arr1,arr2,arr3 };
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;
}
#include<stdio.h>
int main()
{
char* arr[5] = { "hello world","hehe","C","amazing","C++" };//产生5个首字符的地址
//char表示每个元素指向的类型是char,*代表arr是地址
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%s\n", arr[i]);
}
return 0;
}
三、数组指针
- 数组指针 - 是指针,指向数组的指针
- 字符指针 - 指向字符的指针
- 整型指针 - 指向整型的指针
- 浮点型的指针 - 指向浮点型的指针
数组指针和指针数组很容易混淆。为了理解记忆,我们看arr先和谁结合。arr先与*结合为*arr,说明是指针;arr先与[ ]结合为arr[ ],说明是数组。
注意:[ ]的优先级要高于*号的,所以必须加上( )来保证arr先和*结合。
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
p = &arr;//p是用来存放数组的地址,p是数组指针
//我们看这样写 int * p[10];//这是一个指针数组,一共10个元素,每个元素是int类型
//既要有数组的元素,也要有指针的元素
//所以,我们希望p首先是指针,不能和[]结合
//那么,形如int * p,这个*代表指针。那么我们也在p旁边安插*
//int (*p)[10] = &arr;//先看到*p,说明p是指针,向后看[10]表示指向的是数组的,数组的每个元素的类型是int
char* arr2[5];//指针数组
char*(*pc)[5] = &arr2;//首先是数组指针,所以(*pc),指向的是数组,共5个元素,所以写[5],数组的每个元素是char*
int arr3[] = { 1,2,3 };
int(*p3)[] = &arr3;//err int(*)[0]和int(*)[3]数组的下标不同,所以此处不写大小时,会默认是0
int(*p3)[3] = &arr3;
return 0;
}
数组指针到底有什么用呢?我们通过代码进行了解。
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int(*p)[10] = &arr;//这样写,确实把数组地址放在p中去了,但是拿p去访问数组中的元素其实是非常不方便的。
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", (*p)[i]);//p中放的是&arr,取地址&和解引用*是可以相互抵消的,所以解引用p可以得到数组名(*&arr)
printf("%d ", p[i]);//err,p[i]等价于*(p+i),而p中存放的是&arr,所以每次+i都会跳过整个数组的长度
//arr等价于p
}
return 0;
}
所以在一维数组中不适用数组指针,往往在二维数组中使用,例如:
#include<stdio.h>
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");
}
}
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);
return 0;
}
我们知道,数组名是首元素的地址。所以,上述代码可修改为:
#include<stdio.h>
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]);
}
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是二维数组的数组名,是首元素的地址,首元素的地址就是第一行的地址
//第一行的地址就是一维数组的地址,那么上边就需要一个指针,指向一维数组来接收
return 0;
}
#include<stdio.h>
void print(int arr[], int sz)//即使写成数组的形式,也不会真实的创建一个数组,所以这里arr[]方括号中可以省略,但二维数组中,只能省略第一个方括号中的数组,即行可以省略,列不可以
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
void print(int *arr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);//arr[i]等价于*(arr+i)
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
print(arr, sz);
return 0;
}
int arr[5];//arr是一个能够存放5个整型数据的数组
int* parr1[10];//parr1是一个数组,数组10个元素,每个元素的类型是int*
int(*parr2)[10];//parr2是指针,该指针是指向数组的,指向的数组有10个元素,每个元素的类型是int
int(*parr3[10])[5];//parr3是一个数组,是存放数组指针的数组,数组有10个元素,存放的这个数组指针,指向的数组有5个元素,每个元素的类型是int
四、数组参数、指针参数
在写代码时难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?
4.1 一维数组传参
数组传参,形参是可以写成数组形式的;数组传参的本质是传递数组首元素的地址,所以,数组传参,形参也可以是指针。
void test(int arr[])//ok//可以不写,也可以写成任意一个数
{}
void test(int arr[10])//ok
{}
void test(int*arr)//ok
{}
void test2(int*arr[20])//ok//可以不写,也可以写成任意一个数
{}
void test2(int**arr)//ok//我们说,数组名是首元素的地址,数组中每个元素都是int*类型。
{}
int main()
{
int arr[10] = { 0 };
int* arr2[20] = { 0 };
test(arr);
test2(arr2);
}
4.2 二维数组传参
void test(int arr[3][5])//ok
{}
void test(int arr[][])//no//行可以省略,列不能
{}
void test(int arr[][5])//ok
{}
void test(int* arr)//no
{}
void test(int* arr[5])//no
{}
void test(int(*arr)[5])//ok
{}
void test(int** arr)//no
{}
int main()
{
int arr[3][5] = { 0 };
test(arr);
}
4.3 一级指针传参
#include<stdio.h>
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(int*p)
{}
int a = 10;
int* ptr = &a;
int arr[5];
test(&a);//传整型一维数组的数组名
test(ptr);//传整型变量的地址
test(arr);//传整型指针
4.4 二级指针传参
#include<stdio.h>
void test(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int* p = &n;
int** pp = &p;
test(pp);
test(&p);
return 0;
}
当函数的参数为二级指针的时候,可以接收什么参数?
void test(int ** p)
{}
int n = 10;
int* p = &p;
int** pp = &p;
int* arr[6];
test(&p);
test(pp);
test(arr);
五、函数指针
数组指针 - 指向数组的指针 - 存放的是数组的地址 - &数组名 就是数组的地址
函数指针 - 指向函数的指针 - 存放的是函数的地址 - 怎么得到函数的地址呢?是不是&函数名?
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
printf("%p\n", &Add);//005D13B6
//说明,&函数名 就是函数的地址
printf("%p\n", Add);//005D13B6
//函数名也是函数的地址,二者没有区别
//int(*pf1)(int, int) = Add;//pf1就是函数指针变量
int(*pf2)(int, int) = &Add;//明白说明pf2就是一个指针,向后看到圆括号,说明是函数,函数的参数类型是int,返回类型是第一个int
//写法与数组指针非常类似
//int ret = (*pf2)(2, 3);//解引用,这样的写法是对应习惯
//printf("%d\n", ret);
//我们知道
//int ret = Add(2, 3);//既然Add是函数的地址,同时pf2也是函数的地址,那么如下形式:
int ret = pf2(2, 3);
printf("%d\n", ret);
//由此可以看出,圆括号中的*其实是没有起作用的
return 0;
}
接下来,我们看两个比较有意思的代码,来对函数指针做进一步了解。
#include<stdio.h>
int main()
{
(*(void(*)())0)();
//从0开始看,0是整数类型,0之前,看(*),如果是(*p),就是p是指针,指向函数,返回类型是void,p去掉
//void(*)()是一个函数指针类型,类型放在括号中,想要强制类型转化
//强制类型转化后,再解引用,即为调用函数,调用函数括号中没有参数,即没有传参
//综上,是调用0地址处的函数,这个函数没有参数,返回类型是void
return 0;
}
#include<stdio.h>
int main()
{
void (*signal(int, void(*)(int)))(int);
//先从signal下手,signal是函数名,后边圆括号是函数,两个参数一个是整型类型,另一个是函数指针类型
//除上述以外,剩下的也是函数指针类型,void(*)(int)
//综上,这个代码是一次函数声明,声明的是signal函数,signal函数的参数有2个,第一个是int,第二个是void(*)(int),该函数指针指向的函数,参数是int,返回类型是void
//signal函数返回类型也是函数指针类型,该类型是void(*)(int),该函数指针指向的函数,参数是int,返回类型是void
//也可改写
//typedef void(*pfun_t)(int);//类型重命名typedef void(*)(int)命名为pfun_t,但是pfun_t按照规范要求必须放在*旁边
//pfun_t signal(int, pfun_t);
return 0;
}