C/C++指针总结(下)---这些你混淆了吗?

1、常量与指针

(1)常量指针:

格式: const 数据类型 * 名字 = 地址
即指向常量的指针,它不能修改常量的值,但可以指向其他常量和变量

  • 首先需要了解一下常量修饰限定符const,在变量前加上const就表示这是一个常量
const int a=100;
const int * ptr =&a;(或者可以写成 int const * ptr =&a;)

不能写*ptr=200; 因为指针ptr是一个变量,它指向的是一个常量,而常量是不能改变值的.但可以写成ptr=&c;
意思是对指向的数据不能进行修改,只能访问

(2)指针常量

格式: 数据类型 * const 名字 = 地址
指针常量即该指针是常量,既然指针是常量,那么该指针就不能改变,意思是不能再指向其他的变量,不能脚踏两只船,但是可以改变指向变量的值

int  a=100;
int *const ptrA=&a;

不能写 ptrA=&b;但可以写成 *ptrA=1000;

(3)指向常量的常量指针

就是前两者的结合,既不能改变指针的值,也不能改变指向变量的值

conest int a=100;
conest int b=10;
conest int * conest ptr =&a;

这里大家可能会有点分不清楚,这里有个小窍门,就是看const离谁进就限定谁
比如 const int *ptr,这里const相当于int 和
*ptr 来说,离int 更近,所以这是一个指向常量的指针

再看 int *const ptr,const 离ptr近,所以这是一个指针是常量!!

2.函数指针,结构体指针

其实函数和结构体都是有地址的!!!

(1) 函数指针

所以类似于其他变量等,我们也可以用指向函数的指针

格式 数据类型 (*函数名)(数据类型 变量名)

int fun1(int a,int b);
int (*compare)(int a,int b);
compare=fun1;
int result=(*compare)(3,5);

首先我们定义了一个函数fun1和一个函数指针,这个函数指针指向了fun1这个函数,然后后面调用这个函数.

(2)结构体指针

struct MyStruct
{
    int a;
    int b;
    int c;
};
struct MyStruct ss={20,30,40};
//声明了结构对象ss,并把ss 的成员初始化为20,30 和40。
struct MyStruct *ptr=&ss;
/*声明了一个指向结构对象ss 的指针。它的类型是MyStruct *,
它指向的类型是MyStruct。*/
int *pstr=(int*)&ss;
//声明了一个指向结构对象ss 的指针。但是pstr 和
//它被指向的类型ptr 是不同的。

请问怎样通过指针ptr 来访问ss 的三个成员变量?
答案:
ptr->a; //指向运算符,或者可以这们(*ptr).a,建议使用前者
ptr->b;
ptr->c;

又请问怎样通过指针pstr 来访问ss 的三个成员变量?
答案:
*pstr; //访问了ss 的成员a。
*(pstr+1); //访问了ss 的成员b。
*(pstr+2) //访问了ss 的成员c。

这样使用pstr 来访问结构成员是不正规的,为了说明为什么不正规,让我们看看怎样通过指针来访问数组的各个单元: (将结构体换成数组)

int array[3]={35,56,37};
int *pa=array;
//通过指针pa 访问数组array 的三个单元的方法是:
*pa; //访问了第0 号单元
*(pa+1); //访问了第1 号单元
*(pa+2); //访问了第2 号单元

从格式上看倒是与通过指针访问结构成员的不正规方法的格式一样。

所有的C/C++编译器在排列数组的单元时,总是把各个数组单元存放在连续的存储区里,单元和单元之间没有空隙。但在存放结构对象的各个成员时,在某种编译环境下,可能会需要字对齐或双字对齐或者是别的什么对齐,需要在相邻两个成员之间加若干个"填充字节",这就导致各个成员之间可能会有若干个字节的空隙。

所以,在例十二中,即使pstr 访问到了结构对象ss 的第一个成员变量a,也不能保证(pstr+1)就一定能访问到结构成员b。因为成员a 和成员b 之间可能会有若干填充字节,说不定*(pstr+1)就正好访问到了这些填充字节呢。这也证明了指针的灵活性。要是你的目的就是想看看各个结构成员之间到底有没有填充字节,嘿,这倒是个不错的方法。
不过指针访问结构成员的正确方法应该是象例十二中使用指针ptr 的方法。

3. 数组指针

首先看一下数组与指针的区别
在这里插入图片描述

(1)数组也是指针

其实数组也相当与指针(数组名其实是是常量指针,不允许进行运算等操作)

int array[10]={0,1,2,3,4,5,6,7,8,9},value;
value=array[0]; //也可写成:value=*array;
value=array[3]; //也可写成:value=*(array+3);
value=array[4]; //也可写成:value=*(array+4);

上例中,一般而言数组名array 代表数组本身,类型是int[10],但如果把array 看做指针的话,它指向数组的第0 个单元,类型是int* 所指向的类型是数组单元的类型即int。因此array 等于0 就一点也不奇怪了。同理,array+3 是一个指向数组第3 个单元的指针,所以(array+3)等于3。其它依此类推

(2)指针数组

大家接触过整形数组,字符型数组.那么指针数组就是数组里面装的指针(也就是地址)

int a[2][3]={{1,2,3},{4,5,6}};
int *ptr[2];
if(a[0][0]>a[0]][1])
ptr[0]=&a[0][0]

int *ptr[2];定义了一个指针数组,大小为2,即存储两个int型的指针. 比较二维数组元素大小,把较大的地址存储到指针数组的第一个位置. 如果后面要访问就是 *ptr[0] 等价于a[0][0]

再看一个例子


char *str[3]={
    "Hello,thisisasample!",
    "Hi,goodmorning.",
    "Helloworld"
};
char s[80]strcpy(s,str[0]); //也可写成strcpy(s,*str);
strcpy(s,str[1]); //也可写成strcpy(s,*(str+1));
strcpy(s,str[2]); //也可写成strcpy(s,*(str+2));

上例中,str 是一个三单元的数组,该数组的每个单元都是一个指针,这些指针各指向一个字符串。把指针数组名str 当作一个指针的话,它指向数组的第0 号单元,它的类型是char **,它指向的类型是char
str 也是一个指针,它的类型是char ,它所指向的类型是char,它指向的地址是字符串"Hello,thisisasample!"的第一个字符的地址,即’H’的地址。注意:字符串相当于是一个数组,在内存中以数组的形式储存,只不过字符串是一个数组常量,内容不可改变,且只能是右值.如果看成指针的话,他即是常量指针,也是指针常量.
str+1 也是一个指针,它指向数组的第1 号单元,它的类型是char
,它指向的类型是char

(str+1)也是一个指针,它的类型是char,它所指向的类型是char,它指向"Hi,goodmorning."的第一个字符’H’

(3)行指针,列指针

–行指针

行指针和列指针是针对二维数组来说的.
二维数组可以看成是由若干个一位数组构成的

int a[3][4]={0};//定义一个二维数组初始化为0
这里a[0]可以看成是由元素a[0][0],a[0][1],a[0][2],a[0][3],这四个整形元素组成的一维数组的数组名(意思就是我们把a[0]想象成一位数组的数组名)这里a[0]代表的是一维数组第一个元素的a[0][0]的地址,a[0]+1代表元素a[0][1]元素的地址;

所以二维数组特殊的地方在于a是一个地址,a[0],a[1].a[2]同样是地址.
a代表的是这个二维数组的第一个元素a[0][0]的地址,而a[0]代表的是二维数组第0行的地址,a[1]代表的是第一行的地址.
大家可以把二维数组的行地址理解成一个宾馆的楼层号,a[1]代表第一层.然后我们要具体访问哪个房间,只需要房间在这一层的号数即可.

那么怎么表示行指针呢?
int (*p)[4]. 定义了一个指向含有4个元素的一维整形数组的指针变量,这里p就是行指针. int 代表行指针所指向的一位数组的类型,[4]代表数组的大小;

那么怎么访问?


int A[2][3]={0};
int (*p)[3];//定义指向三个成员的数组的指针
p=&A[0];//把A的第一行的地址赋给p
cout>>(*p)[2];//访问第一行第三个元素

这里也可以写成cout<<*(p+2); 这里p就相当于A[0],即每一行的行地址,由于每一行也是一个一位数组,所以可以像一位数组那样进行加减;

–列指针

int *p=A[0],这里p就是列指针,注意和行指针p=&A[0]的区别,行指针存储的是每一行的地址,列指针存储的是每一行中元素的号地址
p=A[0]<–>p=&A[0][0]<—>p=*A

定义了列指针p后,为了能通过p引用mn的二维数组(m行n列),可通过(p+i×n+j)来访问
比如*(p+1n+2)就相当于访问第二行的第二列的数字
i
n 表示跳过多少行
+j 表示在每一行中的哪一号

4.二级指针与指针作形参

(1)二级指针

既然有指向变量的指针,那么也有指向指针的指针,我们俗称为二级指针,有二级指针当然又有三级,四级,多级指针,但我们用的较多的是二级指针,所以这里着重讲的是二级指针.

指向指针的指针?,因为我们提到指针变量也是一个变量,它在内存中也有地址,所以我们可以用另外一个指针来保存这个指针变量的地址,具体如下

int a=5;
int *p=&a;
int **ptr=&p;

我们这里p是一个一级指针,p指向a,然后又定义了一个ptr,大家可以看到这个ptr前有两个**号,没错,这个ptr就是二级指针,表示
他存储的就是p指针的地址.不清楚?我们来看图
在这里插入图片描述
我们知道指针是有地址的,比如此时p指针的地址是0x1078,那我们int **ptr=&p;就是把p的地址0x1078给ptr保存,以后我们要修改p的值,就可以通过间接访问ptr来实现.
比如我们要修改指针p的值,那我们可以

int *s=NULL;
*ptr=&s;
//就相当于p=&s;

(2)指针作函数的形式参数

有时候,我们的函数的形式参数不一定就是传入整形的变量,或char型的字符等,有时我们会传入地址(这里传地址有几点好处,打家可以看我上一篇文章)

举例如下:
大家看一下这两个例子,哪个是正确,哪个是错误的?
(1)

#include <iostream>
using namespace std;
 
void GetMemory(char *p, int num)
{
	p = (char*)malloc(sizeof(char)*num);
}
 
int main()
{
	char *s = NULL;
	GetMemory(s, 100);
	strcpy(s, "hello");
	printf(s);
	return 0;
}

(2)

#include <iostream>
using namespace std;
 
void GetMemory(char **p, int num)
{
	*p = (char*)malloc(sizeof(char)*num);
}
 
void main()
{
	char *s = NULL;
	GetMemory(&s, 100);
	strcpy(s, "hello\n");
	printf(s);
}

这里(1)是错误的!!!为什么?
GetMemory是调用malloc申请一块内存。乍一看好像没什么问题,编译也不会报错。但是运行起来,程序直接奔溃。 其实有了上面的分析就可以知道,我们要间接改变一个变量或指针的值,我们得把这个变量或指针的地址给它,然而main函数中我们传入的不是指针s的地址,然而我们的Getmemory的形式参数char **p必须接收指针的地址,所以你这里牛头不对马嘴,所以会程序会崩掉!!

那我们正确的写法是如(2)所示,传入指针s的地址,即Getmemory(&s),这样形参p保存的就是指针s的地址了,然后我们就可以在Getmemory中通过指针p来间接对s进行操作了.

注意,我们在用指针p时,前面要加上一个*,为什么,因为p是二级指针,我们首先要通过解引得到指针s,相当于这里*p就是指针s

5.指针与引用

(1)指针与引用的关系?

引用是c语言中不含有,是c++中的的操作符,用&表示,注意这和取地址符虽然长得一样,但是在不同的地方有各自不同的用处,就好比世界上两个同名的人,虽然名字一样,但发挥的功能不同.
引用怎么用呢?

int a=10;
int c=2;
int &b=a;
b=20;

解释一下 int &b=a是什么意思,就是定义了一个b,把a的地址给它,前面提到引用实际上是一个指针常量,所以这里在底层代码中,是这样表示的 int * const b=&a;
但是人嘛毕竟是要偷懒的是吧!!所以为了我们使用方便,我们写成int &b=a而不是int &b=&a,这样以后就可以直接用b进行操作,
比如b=20 而不是b=20,我们就是对b进行修改,那么有小伙伴会问?
改了b以后,a的值会不会变呢?答案是肯定要变的因为我们b=20实际上是
b=20,所以懂了吗?

还有就是我们不能写了int &b=a后,又写int &b=c,这是不允许的,因为我们讲过,引用是指针常量,不能脚踏两只船!!!

(2)引用能简化形式参数

先看一个简单例子

用了引用

#include <iostream>
 
using namespace std;
 
void swap(int&, int&);
int main(){
	int a = 3, b = 4;
	cout << "a=" << a << ", b=" << b << endl;
	swap(a, b);
	cout << "a=" << a << ", b=" << b << endl;
	system("pause");
	return 0;
}
 
void swap(int &x, int &y){
	int t = x;
	x = y;
	y = t;
}

其实swap函数底层是这样表示的

void swap(int *const x,int *const y){
	int t=*x;
	*x=*y;
	*y=t;
}

不用引用

#include <iostream>
 
using namespace std;
 
void swap(int*, int*);
int main(){
	int a = 3, b = 4;
	cout << "a=" << a << ", b=" << b << endl;
	swap(&a, &b);
	cout << "a=" << a << ", b=" << b << endl;
	system("pause");
	return 0;
}
 
void swap(int *x, int *y){
	int t = *x;
	*x = *y;
	*y = t;
}

在这里插入图片描述
两种效果一样

再看一个例子

#include <iostream>
using namespace std;
 
void GetMemory(char **p, int num)
{
	*p = (char*)malloc(sizeof(char)*num);
}
 
void main()
{
	char *s = NULL;
	GetMemory(&s, 100);
	strcpy(s, "hello\n");
	printf(s);
}

这里形式参数是用的char **p,这里可以用引用来简化,我们可以写成char *&p.
然后 Getmemory(&s,100)写成Getmemory(s,100)

什么意思?清楚点!!!意思写成如下代码

#include <iostream>
using namespace std;
 
void GetMemory(char *&p, int num)
{
	p = (char*)malloc(sizeof(char)*num);
}
 
void main()
{
	char *s = NULL;
	GetMemory(s, 100);
	strcpy(s, "hello\n");
	printf(s);
}

我们一步一步来?

首先我们传入s,不是&s.因为在引用里面,我们写的s其实上是&s,只不过在编译的时候编译器偷偷给你换掉了.所以我们用引用&p来接收s,根据上面的分析,我们知道引用可以直接用变量名表示你引用的对象,所以&p就相当于是s,那好我们接受了以后要声明这是什么类型的引用呀!是引用的整形指针,还是字符型指针,所以我们在前面加上char *,表示引用字符型指针.
所以p就相当于s了,大家认为这样写是不是比写两个**号要简单些呢?

由于篇幅原因,有些知识没有讲到,所以
我在这里附上两篇有关引用,函数传参的博客,写得很好!!!
C++中函数调用时的三种参数传递方式详解
C++ 引用与引用作为函数的参数

最后附上一个学院的b站二维码,里面很多视频是免费的,有很多大佬为你解答疑惑,还有很多活动,大家可以在这里进行交流

猜你喜欢

转载自blog.csdn.net/weixin_46273997/article/details/106828810