C++基础学习(五)C++指针初步理解

指针对于学习C/C++来说是座难以翻越的大山,我们有太多的脑细胞前仆后继死在了这上面,然而指针也是C++的精华所在,越不过指针这一关,那C++自然也没法往下学,关于C++中的if判断语句和forwhiledo...while等语句就不做具体总结。

一、内存与地址

     要理解指针必然要先理解指针所操作的对象,指针也属于变量,它里面存储的就是内存中的地址。

    内存与地址的基本关系如下图:


       当我们用C++规定好的规则写好代码,再编译后运行,最终结果输出在屏幕上,这其中需要计算机各种资源的协助,内存就是这些计算机资源的分配这和管理者,你的程序需要计算,内存调用计算资源;你需要进行文件的读写,内存为你创建文件读写流;当你需要建立网络连接,内存调用计算机的网络资源......

        一般我们是看不到内存的工作过程的,在有了指针后,我们可以实现对内存的操作,这很让人激动,但有时要是操作不当也很危险,内存中有我们计算机运行的重要资源,要是动了他们,电脑瘫痪,系统崩溃也不是不可能。


二、指针的基本使用

指针的使用十分灵活,用途也很广,同时也有很多操作细节,光看书和听课很难理解指针,自己实际操作再结合内存地址分析,才能更好的理解指针,下面是一些指针的基本使用方法,应该还有不少用法,大家可以自己去探索和挖掘。

#include<iostream>

using namespace std;

void main()
{
	int price = 203;      //先定义一个普通变量

	//int *pprice = &price;   //这种*号在变量名左上方的方式也是正确的,看个人习惯
	int* pprice = &price;     //这就是声明了一个指针,*现在表示该变量是指针变量
	                          //&号就是取地址符号,将price变量在内存中的地址赋给指针

	//pprice就是个地址,会输出一串十六进制的数字,这里的*号为间接运算符,可以取得对应地址中存放的值
	cout << "地址:" << pprice << " 地址中的值:" << *pprice << endl;
}

你可以重复运行该程序几次,会发现每次输出的地址值都不一样,这是因为每次内存分配的地址是不固定的,这些地址是空闲和可用的就行。

可以定义任意类型的指针,基本数据类型、复合类型、函数、类等,类型越复杂,你对指针的理解也要越详尽。下面是关于复合类型指针的用法

#include<iostream>
#include<vector>
#include<string>      

using namespace std;

void main()
{
	vector<int> myVector;
	vector<int>* pVector = &myVector;   //定义vector<int>类型的指针,并将

	pVector->push_back(100);    //可以通过 -> 来调用指针中对象的变量、方法
	pVector->push_back(200);

	cout << myVector[0] << "  " << myVector[1] << endl;   //查看使用指针来添加元素是否成功

	struct Student     //定义结构体
	{
		int age;
		bool isBoy;
		string name;
	};

	Student student;    //实例化一个学生结构体

	Student* pStudent = &student;   //定义结构体指针

	pStudent->age = 18;             //使用指针为结构体变量赋值
	pStudent->isBoy = true;
	pStudent->name = "xiaoming";

	//输出结构结构体中的值
	cout << "年龄:" << student.age << " 是否为男孩:" << student.isBoy << " 姓名:" << student.name << endl;
	//通过指针访问结构体中的值
	cout << "年龄:" << pStudent->age << " 是否为男孩:" << pStudent->isBoy << " 姓名:" << pStudent->name << endl;
}

关于空类型指针:

空类型指针有点类似与我们之前提到的auto关键字,auto可以接收任意类型的变量,同样的void*也可以接收任意类型的指针。

#include<iostream>

using namespace std;

void main()
{
	void* pValue1 = nullptr;    //空类型的指针
	int value1 = 1200;
	pValue1 = &value1;    //这种指针会接收任何其他类型的指针

	//cout << *pValue1 << endl;   //然而当你想访问该指针时必需为其强制转换指针类型
	cout << *((int*)pValue1) << endl;   //这种方式才对
}

三、指针与常量

关于指针与常量之间有不少细节,因为内存分配的地址是变动的,而常量又是始终不变的。

#include<iostream>

using namespace std;

void main()
{
	//1.指向常量的指针
	const int myValue = 110;

	//定义一个指向常量的指针,该指针指向的内容不可修改,但该指针可以指向其他地址
	const int* pValue = &myValue;
	//输出为110
	cout << *pValue << endl;

	//这里又有一个常量
	const int myChange = 990;
	//还是使用上面的指针去指向这个地址
	pValue = &myChange;
	//输出为990
	cout << *pValue << endl;


	//2.常量指针
	int data = 123;
	//现在该指针为常量指针,意味着该指针指向的地址不可更改,它只能指向data变量所在的地址
	int* const pConstPoint = &data;
	cout << *pConstPoint << endl;

	//但data不是常量,它可以被更改,
	data = 999;
	//输出此时data修改后的值,为999
	cout << *pConstPoint << endl;


	//3.指向常量的常量指针
	const int myValue2 = 888;
	//这就是指向常量的常量指针,指针所指的地址不可变,地址中的值也不可变,这里有两个const
	const int* const pConstValue = &myValue2;
}

  • 常量不可更改,但常量指向该常量的指针可以修改;
  • 常量指针所指地址不可更改,但该地址中存放的值可以更改;
  • 当指针为常量指针,指向的地址中存放的也是常量时,这两者都不可修改。


四、指针与数组

数组也属于复合数据类型,但用数组指针可以实现指针的运算。

#include<iostream>

using namespace std;

void main()
{
	//其实数组本身就是一个保存了数组首个元素地址的变量
	int myArray[10];
	//直接输出数组数组的变量名,此时输出的就是myArray[0]的地址,这两者相等
	cout << myArray << "  首个元素的地址:" << &(myArray[0]) << endl;

	//这里给数组指针赋值需要使用{}
	int* pArray[] {myArray};
	//也可以这样赋值,最终这两者的操作方式都一样
	int* pArray2 = myArray;

	//通过循环给数组中的每个元素赋值
	for (int i = 0; i < 10; i++)
	{
		myArray[i] = i * 10;
	}

	//遍历查看数组中的元素
	cout << "常规遍历方式:";
	for (int i = 0; i < 10; i++)
	{
		cout << myArray[i] << " ";
	}
	cout << endl;

	//现在我们通过操作指针遍历数组
	cout << "指针遍历方式:";
	//数组指针其实是有两层地址,
	for (int i = 0; i < 10; i++)
	{
		//pArray指针指向的地址中存放的也是地址,这个地址中才存有我们的数据
		cout << "指针所在地址:" << pArray + i
			<< " 指针所指向的地址: " << *(pArray + i)
			<< " 地址中的值: " << *((*pArray) + i) << endl;
	}

	int a = 100;
	//通过指针修改数组中的值,注意赋值的得是变量
	*(*pArray) = a;
	cout << myArray[0] << endl;
}

这里有指针的加减运算,还涉及到指针的指针,可能有点绕,但这也只是指针的基础知识,我会用图来解释:


内存会为数组分配一段连续的内存空间来存储他们,这一段空间的地址也是连续的,我们可以通过移动指针到相应的地址,再获取该地址中存放的值。

关于指针和数组的内容还有不少,这里想讲清楚篇幅就太大了,大家可以仔细查阅相关的内容。


五、动态分配内存

动态的内存分配是在程序运行期间分配存储数据所需要的内存,不是在编译时分配预定义的内存空间,这让我们可以更加灵活地使用计算机内存,减小浪费。

运算符newdelete

#include<iostream>

using namespace std;

void main()
{
    double* pvalue = nullptr;   
	pvalue = new double;        //为该指针申请一份内存空间
	*pvalue = 3.1415;           //现在我们可以在该指针所指的地址空间中写入数据了

	delete pvalue;       //释放该指针所指的内存,现在那块内存可以被重新分配了
	pvalue = nullptr;    //最后还要重新设置下指针,因为他原来所指向的地址已经不归他所有了


	double* pdata{ new double[20] };   //申请一份数组空间
	delete[] pdata;    //这是释放地址空间的写法
	pdata = nullptr;   //不要忘了重新设置指针	
}

虽然用法很简单,但在实际开发中使用的时机和方式都是需要仔细思量的,而且动态内存分配是存在风险的,可能会出现两种问题:

1、内存泄漏:这也是最常见的问题,在你new分配空间时,使用完该内存后没有释放它,就会出现内存泄漏,                            delete释放内存是十分重要的。

2、自由存储区的碎片:当你的程序频繁地分配和释放内存是有可能会出现这个问题,不过现在基本不常见,由于                                     现在计算机的虚拟内存提供了很大的内存,想详细了解的同学可以百度或维基。

 
 

六、引用

既然有了指针,就必然会牵扯到引用,这两者看上去很相似,实际上完全不同,指针操作的是某个对象的地址,而引用只是作为某个对象的别名。

#include<iostream>

using namespace std;

void main()
{
	int myValue = 34;

	int& rvalue = myValue;      //& 定义引用,类似于指针,但与指针完全不同
	                            //引用是一个名称,可以用作某个对象的别名
	cout << rvalue << endl;    //这里可以直接访问引用中的值,不用像指针需要解除引用
	rvalue += 66;
	cout << rvalue << " " << myValue << endl;  //引用的修改,原来的变量值也会修改

	int* pvalue = &rvalue;   //将引用的地址交给指针
	*pvalue = 888;           //通过地址修改引用的值
	cout << rvalue << " " << myValue << endl;  //原始变量也一样修改了,所以说rvalue只是myValue的别名	
}
虽然现在看上去引用有些鸡肋,但它也提供了一些异常强大的功能,有些时候没有引用,一些操作就无法完成,这个只能以后详细讲了。


总结:指针时C++基础学习的一大难点,很多时候,如非必要,最好不要使用指针,一是使用指针存在一定风险,二是当指针用多了也会很乱,二重指针还好,三重四重指针真的可怕。


猜你喜欢

转载自blog.csdn.net/qq_37873310/article/details/80539442