跟我从零开始学C++(⭐指针专项⭐)超详细版

引言

  小伙伴们,加油呀,上一章我们已经提到了有关指针的学习,本来计划接着学习的,但是想了一下,稍后可能大家会时不时的回来翻看学习,索性我直接把指针有关的所有基础问题一次性全部总结到一章,方便大家到时候翻看复习,对此本章新用了锚点跳转功能,方便大家能快速翻阅自己要看的内容还可以快速回到目录。而且小杨在每一小节都添加了代码部分,为的是帮大家更好的理解和掌握一些片面的定义。小伙伴们也可以跟着小杨一起来敲一敲,试试看有没有理解清楚。

仍然废话不多说,我们开始吧,冲冲冲!!!

指针和引用

目录

1.指针的定义和使用
2.输出较大的数
3.算数运算符
4.关系运算
5.多级指针
6.空指针
7.指针数组
8.指针访问数组
9.指针访问二维数组
10.常量指针和指针常量
11.动态分配内存
12.malloc函数管理内存
13.引用

1.指针的定义和使用

   在介绍指针之前,我需要先引入一个概念,那就是内存地址的概念(计算机内存是由一系列连续的存储单元组成的,每个存储单元都有一个唯一的地址)变量就是存储在内存中的某个地址,我们可以通过地址来访问变量的值。

  • 指针的定义
    那指针是什么呢,指针是一个特殊的变量,它的作用是用来存储另一个变量的地址。指针变量的定义通常包含一个星号*,表示这是一个指针变量。
  • 指针的声明和初始化:
    声明指针时,需要指定它指向的数据类型,初始化指针时,可以将其设置为NULL,或者指向一个同类型变量的地址。
  • 指针的使用
    使用&操作符可以获取一个变量的地址。
    使用*操作符可以获取指针指向地址的值,这称为解引用操作。
  • 代码展示:
#include <iostream>

using namespace std;

int main()
{
	//	定义整型指针p1
	int* p1;

	int i = 10;
	//	&取地址运算符。获取整型变量i的地址并赋值给p1。
	p1 = &i;  // 也称为p1指向i

	cout << "p1: " << p1 << endl;

	//	*解引用运算符。通过指针访问变量。
	*p1 = 100;
	cout << "i: " << i << endl;


	return 0;
}

回到目录

2.输出较大的数

  我们既然已了解了指针的简单的用法,我们可不可以试试看,利用指针来判断一下变量的大小,在借助指针输出的一个简单的小问题了,我们一起来试试看吧

  • 代码展示:
#include <iostream>

using namespace std;

int main()
{
	int a = 20, b = 10;
	int* p, * p1, * p2;
	//	p1保存a的地址,p2保存b的地址
	p1 = &a;
	p2 = &b;
	cout << "p1: " << p1 << " p2: " << p2 << endl;
	//	如果a大于b,交换p1和p2的地址
	if (*p1 > *p2)
	{
		p = p1;
		p1 = p2;
		p2 = p;
	}
	cout << "a: " << a << " b: " << b << endl;
	cout << "max: " << *p2 << endl;
	return 0;
}

回到目录

3.算数运算符

  指针的算术运算符在C和C++语言中是指针操作的重要部分。以下是关于指针算术运算符的基本知识:

  • 自增运算符(++):
    ptr++ 或 ++ptr:将指针移动到下一个数据位置。
    对于指向数组的指针,这通常意味着移动到数组的下一个元素。
  • 自减运算符(–):
    ptr-- 或 --ptr:将指针移动到前一个数据位置。
    对于指向数组的指针,这通常意味着移动到数组的上一个元素。
  • 加法运算符(+):
    ptr + index:将指针向后移动index个数据位置。
    这对于数组访问非常有用,可以用来访问数组的特定元素。
  • 减法运算符(-):
    ptr - index:将指针向前移动index个数据位置。
    这也可以用来访问数组的元素,或者计算两个指针之间的距离。
  • 赋值运算符(=):
    ptr = &value:将指针赋值为变量value的地址。
    ptr = ptr1 + index:将指针赋值为另一个指针加上一个整数的值。
  • 代码展示:
#include <iostream>

using namespace std;

int main()
{
	//	定义整型数组
	int a[5] = { 1, 2, 3, 4, 5 };
	//	定义整型指针
	int* p = &a[0];

	for (int i = 0; i < 5; i++)
	{
		cout << &a[i] << '\t';
	}

	cout << endl << "(p + 1): " << (p + 1) << endl;

	double a1[5] = { 1.1, 1.1, 1.1, 1.1, 1.1};
	double* p1 = &a1[0];
	
	for (int i = 0; i < 5; i++)
	{
		cout << &a1[i] << '\t';
	}

	cout << endl << "(p1 + 1): " << (p1 + 1) << endl;


}

回到目录

4.关系运算

我们在进行关系运算的时候,会用到的是比较运算符。

  • 比较运算符(==, !=, <, >, <=, >=):
    指针之间可以进行大小和等价比较,通常用于数组的遍历和条件判断。
  • 代码展示:

#include <iostream>

using namespace std;

int main()
{
	int a[5] = {};
	for (int i = 0; i < 5; i++)
	{
		cout << &a[i] << '\t';
	}
	cout << endl;
	int* p1 = &a[0], * p2 = &a[3];
	//	比较两个指针的大小,指针地址对应的16进制数字大的指针变量大。
	cout << (p1 > p2) << endl; // false
	cout << (p2 > p1) << endl; // true

	int m = 1, n = 1;	
	int* p3 = &m, *p4 = &n, *p5 = &n;
	cout << "&m: " << &m << " &n: " << &n << endl;
	cout << "p3: " << p3 << " p4: " << p4 << " p5: " << p5 << endl;
	cout << "&p3: " << &p3 << " &p4: " << &p4 << " &p5: " << &p5 << endl;

	cout << (m == n) << endl; // 比较m和n的值,结果为true。
	cout << (p3 == p4) << endl;	//	比较p3和(m的地址)p4的值(n的地址),结果为false。
	cout << (*p3 == *p4) << endl; // 比较p3指向的变量的值和p4指向的变量的值, 结果为true。
	cout << (p4 == p5) << endl; // 比较p4和(n的地址)p5的值(n的地址),结果为true。
	
	//	指针不相等时,两个指针指向不同的变量。
	*p3 = 100;
	cout << "*p3:" << *p3 << " *p4:" << *p4 << endl;
	//	指针相等时,两个指针指向同一个变量。
	*p4 = 200;
	cout << "*p4:" << *p4 << " *p5:" << *p5 << endl;

	return 0;
}

回到目录

5.多级指针

  多级指针是指针的指针,即一个指针变量用来存储另一个指针变量的地址。在C和C++中,多级指针的概念是相对直接的。
  首先,回顾一下一级指针的概念。一级指针是一个变量,它存储了另一个变量的内存地址。二级指针是一个指针,它存储了一级指针的地址。对于三级指针、四级指针等等,可以依此类推。每增加一级指针,就需要多一个星号*。多级指针的访问需要使用多个解引用操作符*。

例如,要访问二级指针pptr指向的一级指针ptr指向的变量var,需要两次解引用。多级指针在复杂的数据结构(如树、图、链表等)和函数中传递指针参数时非常有用。它们允许函数修改指针本身,而不仅仅是指针指向的值。

  • 注意事项:

    • 多级指针会增加代码的复杂性,因此在不必要的情况下应避免使用。
    • 使用多级指针时,必须确保每一级都是正确初始化和管理的,以防止内存错误。
    • 多级指针是C和C++中强大但复杂的特性,理解它们对于深入掌握这些语言至关重要。在实际编程中,多级指针的使用应该谨慎,并确保代码的清晰性和可维护性。
  • 代码展示:

#include <iostream>

using namespace std;

int main()
{
	int a = 100, * p1, *p3;
	
	//	定义二级指针
	int** p2;

	p1 = &a;
	p2 = &p1;

	cout << "&a: " << &a << " p1: " << p1 << " &p1:" << &p1 << " p2:" << p2 << endl;
	cout << "*p2: " << *p2 << " **p2:" << **p2 << endl;

	//	定义三级指针
	int*** p4 = &p2;
	cout << "*p4: " << *p4 << " **p4:" << **p4 << " ***p4:" << ***p4 << endl;

	double p5;
	cout << "sizeof(p1): " << sizeof(p1) << " sizeof(p2): " << sizeof(p2) << " sizeof(p3): " << sizeof(p3)
		<< " sizeof(p4): " << sizeof(p4) << " sizeof(p5): " << sizeof(p5) << endl;

	return 0;
}

回到目录

6.空指针

  空指针是一个特殊的指针值,它不指向任何有效的内存地址。在C和C++中,空指针的概念非常重要,因为它提供了一种表示指针“无值”或“未初始化”的方法。

  • 代码展示:
#include <iostream>

using namespace std;

int main()
{
	
	//	NULL 代表空其值为0,值为NULL的指针称为空指针。指针没有指向时可以设置其值为空。
	int* p1 = NULL;

	if (p1 == NULL)
	{
		cout << "p1是空指针" << endl;
	}
	else
	{
		*p1 = 100;
	}

	cout << "p1: " << p1 << endl;
	//	x64选项下,指针变量占用内存是8字节;x86选项下,指针变量占用的内存空间是4字节。  
	cout << "sizeof(p1): " << sizeof(p1) << endl;
	
	return 0;
}

7.指针数组

  指针数组是一个数组,其元素是指针。换句话说,指针数组的每个元素都存储了一个内存地址,这个地址指向某个数据。在C和C++中,指针数组的使用非常普遍,尤其是在处理字符串数组、动态数据结构等方面。

  • 指针数组的定义方式是在数组名后面加上指针的星号*,然后指定数组的大小。
  • 指针数组可以通过初始化列表来初始化。每个元素都应该是一个指向相应数据类型的指针。
  • 指针数组可以通过初始化列表来初始化。每个元素都应该是一个指向相应数据类型的指针。
  • 代码展示:
#include <iostream>

using namespace std;

int main()
{
	int a = 10, b = 20, c = 30;
	
	//	定义并部分初始化指针数组
	int* p1[3] = { &a, &b};
	
	cout << p1[2] << endl; //	部分初始化时,没有对应初值的指针会被置空。
	
	p1[2] = &c; //	指针数组中的元素赋值

	int sum = 0;
	for (int i = 0; i < 3; i++)
	{
		sum += *p1[i];
	}
	cout << "sum: " << sum << endl;
	
	//	数组名是数组第一个元素的地址
	cout << "p1: " << p1 << " p1[0]: " << p1[0] << " &a: " << &a << " &p1[0]: " << &p1[0] << endl;
	//	通过指针数组名访问指针数组第二个元素
	cout << "p1 + 1" << p1 + 1 << " *(p1 + 1)" << *(p1 + 1) << endl;
	//	通过指针数组名访问第二个元素指向变量的值
	cout << " **(p1 + 1)" << **(p1 + 1) << endl;

	return 0;
}

回到目录

8.指针访问数组

  • 可以使用数组索引来访问指针数组的元素,并通过解引用来获取或修改所指向的值。
  • 代码展示:
#include <iostream>

using namespace std;

int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };

	int* p = a;
	//	获取数组a第二个元素的地址
	cout << "(p + 1): " << (p + 1) << endl;

	//	获取数组a第二个元素的指向的变量的值
	cout << "*(p + 1): " << *(p + 1) << endl;
	
	//	指针自增后,指向数组a第二个元素
	p++;
	//	获取数组a第三个元素的地址
	cout << "(p + 1): " << (p + 1) << endl;

	//	获取数组a第三个元素的指向的变量的值
	cout << "*(p + 1): " << *(p + 1) << endl;

	//	获取数组a第一个元素的地址
	cout << "(p - 1): " << (p - 1) << endl;

	//	获取数组a第一个元素的指向的变量的值
	cout << "*(p - 1): " << *(p - 1) << endl;

	//	指针指向数组第一个元素
	p = a; //相当于p = &a[0]

	//	指针运算的方式访问数组元素
	for (int i = 0; i < 5; i++)
	{
		cout << *(a + i) << ", ";
	}
	cout << endl;

	//	数组名[下标]的方式访问数组元素
	for (int i = 0; i < 5; i++)
	{
		cout << a[i] << ", ";
	}
	cout << endl;

	return 0;
}

回到目录

9.指针访问二维数组

  • 指针数组常用于模拟二维数组。例如,可以创建一个指针数组,其中每个元素指向一个一维数组。
  • 代码展示:
#include <iostream>

using namespace std;

int main()
{
	int a[3][5] = {
		{1, 2, 3, 4, 5},
		{11, 12, 13, 14, 15},
		{21, 22, 23, 24, 25}
	};

	//	二维数组的数组名是数组第一个元素的地址
	cout << "a: " << a << " &a[0][0]" << &a[0][0] << endl;

	//	二维数组的名[行位置] 获取该行第一个元素的地址。
	cout << "a[0]" << a[0] << " &a[0][0]" << &a[0][0] << endl;
	cout << "a[1]" << a[1] << " &a[1][0]" << &a[1][0] << endl;
	cout << "a[2]" << a[2] << " &a[2][0]" << &a[2][0] << endl;

	//使用指针数组访问二维数组
	int* p[3] = { a[0], a[1], a[2] };
	//	使用[][]获取指定的数组元素
	cout << "p[1][1]: " << p[1][1] << endl;
	//	使用[]和指针运算符获取指定的数组元素
	cout << "*(p[1] + 1): " <<* (p[1] + 1) << endl;
	//	使用指针运算符获取指定的数组元素
	cout << "*(*(p + 1) + 1): " << *(*(p + 1) + 1) << endl;
	

	int* p1[3] = { a[0], &a[1][1], a[2]};
	//	使用[][]获取指定的数组元素
	cout << "p1[1][1]: " << p1[1][1] << endl;
	//	使用[]和指针运算符获取指定的数组元素
	cout << "*(p1[1] + 1): " << *(p1[1] + 1) << endl;
	//	使用指针运算符获取指定的数组元素
	cout << "*(*(p1 + 1) + 1): " << *(*(p1 + 1) + 1) << endl;
	return 0;
}

回到目录

10.常量指针和指针常量

  • 常量指针
    常量指针是指向常量的指针,意味着通过这个指针不能改变它所指向的值,但是指针本身可以改变指向其他常量。在C和C++中,你可以通过以下方式定义一个常量指针:
const int value = 10;
const int *ptr = &value; // 常量指针,不能通过ptr改变value的值

在这个例子中,ptr是一个指向value的常量指针。你不能通过ptr来修改value的值,但是你可以改变ptr指向其他const int类型的变量。

  • 指针常量
    指针常量是一个其值(即指向的地址)不能被改变的指针,但是你可以通过指针修改它所指向的值。在C和C++中,你可以通过以下方式定义一个指针常量:
int value = 10;
int *const ptr = &value; // 指针常量,不能改变ptr的值(即不能让ptr指向其他变量)

在这个例子中,ptr是一个指向value的指针常量。一旦ptr被初始化为&value,你就不能将ptr改为指向其他变量。不过,你可以通过ptr修改value的值。

  • 两者的区别

    • 常量指针:指针指向的内容是常量,不能通过指针改变这个内容,但指针本身可以改变指向其他常量。
    • 指针常量:指针本身是一个常量,一旦初始化后,就不能改变指向其他变量,但可以改变指向变量的值。
  • 代码展示:

#include <iostream>

using namespace std;

int main()
{
	//	常量指针
	
	//	定义常量指针
	const int* p1;
	//	定义指针
	int* p2;
	//	定义整型变量
	int a = 10;
	//	定义整型常量
	const int b = 20;
	
	p1 = &a;	//	常量指针也可以指向变量
	p1 = &b;	
	//	p2 = &b; 常量指针才能指向常量,变量指针不能指向常量。

	//	*p1 = 100; 不能通过常量指针修改指向的变量的值
	a = 100;

	//	指针常量

	int m = 3, n = 5;
	//	定义指针常量
	int* const p3 = &m;

	//	p3 = &n;  指针常量的指向不能发生改变
	//	指针常量的指向内存地址的内容可以修改
	*p3 = 100;
	cout << "m: " << m << " *p3: " << *p3 << endl;

	char c[] = "abcd";
	char* p4 = c;
	//	"abcd" 是字符串常量,常量指针才能指向常量
	const char* p5 = "abcd";
	cout << "p5: " << p5 << endl;
	cout << "p5: " << *p5 << endl;
	cout << "p5[0]: " << p5[0] << endl;
	


	return 0;
}

回到目录

11.动态分配内存

  动态分配内存是指在程序运行过程中,根据需要向操作系统请求分配内存空间的过程。在C语言中,动态内存分配主要通过标准库函数实现,如malloc、calloc、realloc和free。在C++中,除了可以使用这些C语言的函数外,还可以使用new和delete关键字来进行动态内存的管理。

  • C++中的动态内存分配
    new:分配单个对象的内存,调用构造函数初始化对象。
    new[]:分配数组对象的内存,调用构造函数初始化每个元素。
    delete:释放单个对象的内存,调用析构函数。
    delete[]:释放数组对象的内存,调用每个元素的析构函数。
  • 动态内存分配的用途
    数据结构:动态创建数据结构,如链表、树、图等。
    未知大小的数组:在运行时确定数组的大小。
    避免内存浪费:根据需要分配和释放内存,提高内存使用效率
  • 动态内存分配的注意事项
    内存泄漏:分配内存后未释放会导致内存泄漏,即程序不再使用的内存没有被归还给操作系统。
    野指针:释放内存后,指针变为野指针,应将其设置为NULL或重新分配。
    内存越界:访问分配的内存之外的地址会导致未定义行为。
  • 代码展示:
#include <iostream>

using namespace std;

int main()
{
	//	动态分配单个整型存储空间
	int* p = new int;
	cout << "p: " << p << endl;
	*p = 10;
	int a = *p;
	cout << "a: " << a << " *p: " << *p << endl;

	//	释放动态分配的存储空间。
	delete p;
	p = NULL; // 释放内存空间后,指针置空,避免使用已经释放的内存空间。
	cout << "p: " << p << endl;
	
	//	动态分配单个整型存储空间,并初始化为100
	p = new int(100);  
	cout << "*p: " << *p << endl;  
	delete p;  
	p = NULL;  

	//	动态分配连续的整型存储空间
	int n = 5;
	p = new int[n]; // 申请多少个元素可以使用变量

	p[0] = 1;
	p[1] = 2;
	p[2] = 3;
	p[3] = 4;
	p[4] = 5;	

	for (int i = 0; i < 5; i++)
	{
		cout << p[i] << ", ";
	}
	cout << endl;
	//	释放连续的内存空间。注意[]
	delete []p;
	p = NULL;

	return 0;
}

回到目录

12.malloc管理内存

  当你调用 malloc 时,你需要指定你想要分配的内存大小(以字节为单位)。malloc 会找到足够大的空闲内存块并返回一个指向它的指针。如果分配失败,malloc 返回 NULL。

  • 代码展示:
#include <iostream>

using namespace std;

int main()
{
	/*
		malloc申请动态内存时, 大小由参数决定。
		分配成功后,返回void类型指针,一般需要转换为对应数据类型的指针。
		常见格式: (指针类型) malloc (sizeof(数据类型));

		malloc申请成功时返回申请到的地址,malloc分配失败时,返回NULL;new 分配失败时,抛出异常。
	*/

	int* p1 = (int*)malloc(sizeof(int));
	cout << "p1: " << p1 << endl;
	*p1 = 10;
	
	cout << *p1 << endl;
	//	释放内存使用free。
	free(p1);	
	cout << "p1: " << p1 << endl;
	//	释放后置空指针变量,防止误操作。
	p1 = NULL;

	//	申请连续存储空间
	p1 = (int*)malloc(sizeof(int)*4);
	*p1 = 100;
	*(p1 + 1) = 200;
	p1[2] = 300;
	p1[3] = 400;
	for (int i = 0; i < 4; i++)
	{
		cout << p1[i] << ", ";
	}
	cout << endl;
	
	//	释放连续存储空间
	free(p1);
	p1 = NULL;
	return 0;
}

回到目录

13.引用

  在C++中,引用是一种特殊的别名,它为另一个变量提供了一个替代名称。引用的主要特点是它与其引用的变量共享相同的内存地址,因此对引用的修改实际上就是对引用的变量的修改。C++中的引用与C语言中的指针相似,但引用的语法更为简单,且不需要使用特殊的符号来表示间接访问

  • 引用与指针的区别
    • 引用必须在声明时初始化,而指针可以在任何时候初始化或重新赋值。
    • 引用不可以为空,它必须始终代表一个有效的对象,而指针可以指向空(nullptr)或未初始化。
    • 引用不可以改变其引用的对象,而指针可以改变其指向的地址。

  引用是C++中一个非常强大的特性,它提供了一种简单而直观的方式来处理变量的别名,同时避免了指针的复杂性。正确使用引用可以使得代码更加清晰和易于维护。

  • 代码展示:
#include <iostream>

using namespace std;

int main()
{
	int a = 10;

	//	定义引用b,初始化为a的别名。引用定义时必须初始化。
	int& b = a;

	cout << "b: " << b << endl;

	//	b是a的别名,修改b也会修改a。
	b = 100;
	cout << "a: " << a << endl;

	int c = 20;
	//	赋值操作。引用定义以后不能修改为其他变量的引用。
	b = c;
	cout << "b: " << b << endl;
	cout << "a: " << a << endl;

	//	指针常量形式
	int* const b1 = &a;

	*b1 = 100;
	cout << "b: " << b << endl;
	cout << "a: " << a << endl;

	*b1 = c;
	cout << "b: " << b << endl;
	cout << "a: " << a << endl;

	return 0;
}

回到目录

结语

  小伙伴们,指针的基础部分到这里就结束了感觉怎么样呢,是不是小有难度呢,确实小杨在学指针的时候也是,对这个指针甚是头疼,但是也得咬着牙把这个拿下来,不然后边的内容确实没办法学呢,指针就是分水岭,从这部分大家学不会的就不能接着学了,还有畏难的小伙伴们到这里就停下来了,可是指针又是C++能在计算机领域内独领风骚的核心,只有指针是C++独有的一项技能,我们可以可以用指针做到其他高级语言做不到的功能。所以想要学习C+是绕不过的,小伙伴们一定要加油呀,本章已经把有关指针的一部分讲完了,还有指针和数组和函数相结合的部分,这都是需要我们掌握的,一定要加油坚持下来,我们还要在更好的未来相见呢。

  好了,为今天努力学习的自己鼓鼓掌吧,加油加油,小伙伴们,你们是最棒的!!!冲冲冲!!!

  • 45
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值