C++ 指针详解

目录

1.简言

2.指针是什么

3.指针操作

3.1 &和*运算符

3.2 算数操作

4.指针与数组

4.1数组和指针的关系

4.2数组指针与指针数组

5.const限定符与指针

6.指针与函数

7.void*指针

7.1什么是void*指针

7.2void*指针做函数形参


1.简言

        指针在C++中是一个功能强大,可以直接操作底层内存,但又很容易使用出错的情况,所以使用指针的时候,要避免出现空指针或者野指针的情况。

2.指针是什么

        指针是一种指向其他类型的复合类型。这句话的意思是说指针可以指向其他任何类型,比如int型,float型和自定义类型等,指针是复合类型的意思是指基于其他类型定义的类型。当我们声明指针时是用一个基本数据类型加上类型声明符来定义,例如声明一个int型的指针我们就可以用基础类型int再加上指针类型声明符* 最后紧接这就是指针的名字:

	int* p1= nullptr;    //这是一个指向int型的指针
	float* p2= nullptr;		//这是一个指向float型的指针
	double* p3 = nullptr;	//这是一个指向double型的指针
	char* p4 = nullptr;		//这是一个指向char型的指针
	std::string* p5 = nullptr;		//这是一个指向string型的指针

        指针和普通对象一样可以被声明定义和复制,这意味着,指针在内存中也会分配空间。首先我们先要搞清楚是如何在内存中分配空间的,当我们定义一个变量时,就会在内存中开辟一块空间用来存放这个变量的值,我们常见的int型变量占四个字节,也就是说当我们定义一个int型的变量时,就会在内存中分配一块四个字节大小的空间用来存放int变量的值。

        指针也是一样,当我们定义一个指针是,也会分配一块内存空间,用来存放指针的值。那么分配多大的内存空间呢,存放的值又是什么呢?在32位操作系统中,指针所占的内从空间是4个字节,在64位操作系统中指针所占空间是8个字节,指针存放的值是它所指对象的地址,如图所示:

3.指针操作

3.1 &和*运算符

        我们刚刚已经说过,定义指针时使用一个基本数据类型加上*运算符,而指针的值存放的是个地址,当我们需要对某个变量取地址时,就会用到&取地址符。所以指针需要给指针复制某个地址时我们就可以这样写:int* p=&a;这句话表明了p是一个int*类型的指针,它指向了一个int型的变量a,指针p中存放的是a的地址。

        *运算符只有在声明定义时,表明这个对象是指针。在其他时候,*运算符又叫解引用运算符,解引用的意思是通过指针存放的地址,间接访问这个地址的值,既然可以访问,那当然可以修改这个值:

        

3.2 算数操作

        指针可以加上或减去一个整数,我们常见的指针加一是加的多少呢?这个还要从指针的定义定义说起,定义一个int*的指针,那么指针加法操作就是:指针原有的值+sizeof(int)*加数。

这里我们可以看到,p是一个int*的指针,指向一个int型变量a,当对指针p加一时,实际上加的是四,这是因为sizeof(int)等于四,也就是占四个字节。当对指针q加一时,实际上是加8,这是因为double类型占8个字节。

4.指针与数组

4.1数组和指针的关系

        数组的数组名具有指针的特性,也可以用解引用运算符和执行算数操作。

这里我们定义了一个数组array,数组长度为5。注意:这里的数组名是array,而不是array[5],array[5]只是在定义时表明array是一个长度为5的数组而已。当使用数组名指针的特性时,这个数组名的值是这个数组第一个元素地址的值,对数组名解引用就得到这个数组第一个元素的值。这里的加一家的就是数组元素所占内存的大小,int型占四个字节,每次对数组名加一就是加4,在解引用就得到对应元素的值。

4.2数组指针与指针数组

        数组指针是一个指向数组的指针,而指针数组是一个存放指针的数组。

	int array[5] = { 1,2,3,4,5 };
	int(*p)[5] = &array;	//这是一个数组指针,指向array数组
	int* q[5] = { &array[0],&array[2] ,&array[2] ,&array[3] ,&array[4] };	//这是一个指针数组,里面存放了五个指针

        首先是定义的不同,数组指针实在名字那里加上了括号,而指针数组没有括号。因为[]的优先级比*号高,所以当我们没加括号时,int* q[5]的含义就是这是一个容量为5的数组,数组里存放的类型是int*类型 。而int(*p)[5]的含义就是首先这是一个指针,它指向一个int型的数组,这个数组的容量为5。

        

        其次,我们来分析一下它两用法上的区别,以上面的代码为例。

        第一次输出:首先是对数组指针p的解引用,而数组指针p指向的是array数组,所以对它解引用得到array数组再用数组取下标的方式输出第零个元素的值,输出为1。

        第二次输出:首先还是对数组指针p的解引用,得到的是是数组array,而我们刚刚说过数组名具有指针的特性,所以也可以进行算数操作,这里对数组名+2就是指向的是数组的第三个元素的地址,最后再解引用,得到的就是数组的第三个元素的值,输出为3。

        第三次输出:因为[]的优先级高于*号,所以指针数组右结合,q[0]存放的是一个指针,指向的是array数组的第一个元素的地址,这个时候再解引用得到的就是array数组的第一个元素的值,输出为1。

        第四次输出:首先q[0]得到是一个指针数组第一个元素,既是一个指针,在对这个指针+2,加2后的结果就是array数组的第三个元素的位置,最后在解引用,得到的就是array数组第三个元素的值,输出为3。

5.const限定符与指针

        const限定符是用来限定常量的关键字,当const限定符和指针结合时,更据const关键字所处的位置不同含义也不同,先上概念。顶层const与底层const。顶层const:顶层const表明指针本身是一个常量。底层const:表明指针所指的对象是一个常量。是不是感觉有点绕,没事,上代码:

	int a = 10;
	int b = 20;
	const int* p = &a;		//这是一个底层const
	int* const q = &a;		//这是一个顶层const
	*p = 20;
	q = &b;

        当const放在最前面时,表明这是一个底层const,如代码中的p一样。当const紧跟着指针名时,表明这是一个顶层const。当这是一个底层const时,说明q所指的对象是一个常量,当我们通过指针p间接改变a的值时,是不可以的,所以*p=20;是不合法的,编译器会报错。当这是一个顶层const时,说明指针本身是一个常量,不能改变指针存放的地址,当我们对q重新赋值一个地址时是不可以的,所以q=&b;也是不合法的。

6.指针与函数

        指针不仅可指向基本数据类型,还可以指向函数,指向函数的指针被称为函数指针。这里有一个容易搞混的概念,指针函数。

        指针函数:本质上是一个函数,函数的返回值是一个指针。注意这里不能返回局部变量的指针。本质上:是因为局部变量的生命周期随着函数执行结束而结束,而局部变量分配的内存空间在栈区,当函数执行完后,该空间将被释放。

        函数指针:本质上是一个指针,该指针指向一个函数,定义一个函数指针,需要函数的返回值和形参列表,而与这个函数名称无关。

int(*p)(int a);		//这是一个函数指针,指向一个形参为int类型,返回值为int的函数
int* f(int a);		//这是一个指针函数的函数原型,f是函数名,形参类型为int类型,返回值为int*类型的指针
int* f(int a)
{
	int b = a;
	return &b;		//非法操作,返回的是局部变量b的指针
}

        定义函数函数指针的时候,那个括号一定不能省略,如果省略之后,就会变成一个返回值类型为指针的函数。我们在函数f中,定义了一个局部变量b,并且用a的值赋值给b,但是我们不能返回局部变量b的指针,这是因为,随着函数的执行结束,b所分配的内存空间会被释放,所以不能返回局部变量的指针。

7.void*指针

7.1什么是void*指针

        void*指针是可以指向任意类型的指针,在编译的过程中不会确定其指向的类型,是在程序运行中,确定具体指向的类型。

	int a = 10;
	float b = 20.1;

	int* p = &a;		//int*类型指针p
	float* d = &b;		//float*类型指针d

	void* q = &a;		//void*类型指针q 指向int型
	q = &b;		//void*类型指针q 指向float型
	p = q;		//不能用void*类型指针赋值给int*指针
	d = q;		//不能用void*类型指针赋值给float*指针

        我们是不能够直接用void*型指针赋值给其他类型的指针,但是其他指针是可以赋值给void*类型的指针。如果非要将void*类型指针赋值给其他类型的指针就必须使用强制类型转换,详见代码:

	int a = 10;
	float b = 20.1;

	int* p = &a;		//int*类型指针p
	float* d = &b;		//float*类型指针d

	void* q = &a;		//void*类型指针q 指向int型
	q = &b;		//void*类型指针q 指向float型
	p = static_cast<int*>(q);		//强制转换为int*类型
	d = static_cast<float*>(q);		//强制转换为float*类型

7.2void*指针做函数形参

        void*指针经常被用来当作函数的形参,当void*做函数形参时,更据具体的情况转换成程序需要的类型指针,再进行操作。

enum Type
{
	Int,
	Float,
	Double
};
void func(Type type, void* p)
{
	switch (type)
	{
	case Int:
		std::cout << static_cast<int*>(p) << std::endl;		//强制转换为int*类型
		break;
	case Float:
		std::cout << static_cast<float*>(p) << std::endl;	//强制转换为float*类型
		break;
	case Double:
		std::cout << static_cast<double*>(p) << std::endl;	//强制转换为double*类型
		break;
	default:
		break;
	}
}

        当我们不知道具体传入的指针是什么类型时,就可以用void*指针做函数形参,在函数体内部,更据不同的情况,将void*转换成我们想要的类型。在这个func内,我们用一个枚举来确定需要转变的类型,然后将void*转换成不同的类型,经行操作。

8.总结

        指针是一个功能非常强大可以直接访问内存的利器,但是也要避免出现各种指针使用不当带来的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值