C++ 基础:指针和引用浅谈

指针

基本概念

C++中,指针是存储其他变量的内存地址的变量。

我们在程序中声明的每个变量在内存中都有一个关联的位置,我们称之为变量的内存地址。
如果我们的程序中有一个变量 var,那么&var 返回它的内存地址。

int main()
{int var1 = 3;int var2 = 90;int var3 = 520;cout<<"Address of var1: "<<&var1<<endl;cout<<"Address of var2: " <<&var2<<endl;cout<<"Address of var3: "<<&var3 <<endl;return 0;
}

image-20240625094624250

  • 开头的0x代表十六进制形式的地址。
  • 注意,第一个地址与第二个地址相差 4 个字节,第二个地址与第三个地址相差 4 个字节。
  • 区别是因为在64位系统中int的大小是4个字节
  • 运行该程序时,您可能不会得到相同的结果。这是因为地址取决于程序运行的环境。

如何定义指针:

int* pointer;

以下是我们如何为指针分配地址:

int var = 5;
int* ptr = &var;

在这里,变量 var 被赋值为 5。然后,通过代码 ptr = &var,将变量 var 的地址分配给 ptr指针。

声明指针之后立即对其进行赋值操作是一个不错的编码习惯;

  • 通过指针获取值

需要获取指针指向的变量地址中的值,可以使用*来进行;

int main()
{int var1 = 5;int* ptr = &var1;cout<<"Address of var1: "<<&var1<<endl;cout<<"ptr value: "<< *ptr<<endl;return 0;}

我们使用 *point_var 来获取存储在该地址中的值。当将 * 与指针一起使用时,称之为解引用运算符。它作用于指针并给出指针中存储的地址指向的值。也就是说,*point_var = var

再看一个例子,强化对指针和解引用概念的理解:

#include <iostream>
using namespace std;
int main() {int var = 5;// store address of varint* point_var = &var;// print value of varcout << "var = " << var << endl;// print address of varcout << "Address of var (&var) = " << &var << endl<< endl;// print pointer point_varcout << "point_var = " << point_var << endl;// print the content of the address point_var points tocout << "Content of the address pointed to by point_var (*point_var) = " << *point_var << endl;return 0;
}

Working of C++ Pointers


  • 改变指针指向的值

如果point_var指向var的地址,我们可以使用*point_var来改变var的值。

int main()
{int var1 = 3;int* ptr = &var1;*ptr = 1;cout<<"Address of var1: "<<&var1<<endl;cout<<"ptr value: "<< *ptr<<endl;return 0;}

这里,point_var&var具有相同的地址;当*point_var改变时,var 的值也会改变。

  • 使用指针时候的常见错误用法:
int var = 5;// Wrong! 
// point_var is an address but var is not
int* point_var = var;// Wrong!
// &var is an address
// *point_var is the value stored in &var
*point_var = &var;// Correct! 
// point_var is an address and so is &var
point_var = &var;// Correct!
// both *point_var and var are values
*point_var = var;

指针和数组

C++ 中,指针是保存其他变量地址的变量。指针不仅可以存储单个变量的地址,还可以存储数组单元的地址。

int *ptr;
int arr[5];// store the address of the first
// element of arr in ptr
ptr = arr;
  • 这里,ptr是一个指针变量,而arr是一个int数组。
  • 代码ptr = arr; 将数组的第一个元素的地址存储在变量ptr中。
  • 注意,我们使用的是arr而不是&arr[0]。这是因为它们是相同的。因此,下面的代码与上面的代码相同。
int *ptr;
int arr[5];
ptr = &arr[0];

其余数组元素的地址由 &arr[1]、&arr[2]、&arr[3] &arr[4] 给出。


  • 使用指针获取指定元素

假设我们需要使用相同的指针 ptr 指向数组的第四个元素。
这里,如果 ptr 指向上例中的第一个元素,那么 ptr + 3 将指向第四个元素地址。例如:

int *ptr;
int arr[5];
ptr = arr;ptr + 1 is equivalent to &arr[1];
ptr + 2 is equivalent to &arr[2];
ptr + 3 is equivalent to &arr[3];
ptr + 4 is equivalent to &arr[4];

类似地,我们可以使用单个指针访问元素。例如:

*ptr == arr[0];
*(ptr + 1) is equivalent to arr[1];
*(ptr + 2) is equivalent to arr[2];
*(ptr + 3) is equivalent to arr[3];
*(ptr + 4) is equivalent to arr[4];

假设我们已经初始化了 ptr = &arr[2];

ptr - 2 is equivalent to &arr[0];
ptr - 1 is equivalent to &arr[1]; 
ptr + 1 is equivalent to &arr[3];
ptr + 2 is equivalent to &arr[4];

Working of C++ Pointers with Arrays

int main()
{float arr[3];float *ptr;for(int i=0;i<3;i++){cout<<"&arr["<<i<<"]=" <<&arr[i] <<endl;}ptr = arr;cout<<endl;for(int i=0;i<3;i++){cout <<"ptr + " << i <<"  = " << ptr+i<<endl;}return 0;}

在上面的程序中,我们分别使用普通方式和指针的方式打印了数组元素的地址信息;

在大多数情况下,数组名称会退化为指针。简单来说,数组名称被转换为指针。这就是为什么我们可以使用指针来访问数组元素的原因。但是,我们应该记住,指针和数组并不相同。在某些情况下,数组名称不会衰减为指针。具体情况可以查阅:https://stackoverflow.com/questions/17752978/exceptions-to-array-decaying-into-a-pointer

// C++ Program to insert and display data entered by using pointer notation.#include <iostream>
using namespace std;int main() {float arr[5];// Insert data using pointer notationcout << "Enter 5 numbers: ";for (int i = 0; i < 5; ++i) {// store input number in arr[i]cin >> *(arr + i) ;}// Display data using pointer notationcout << "Displaying data: " << endl;for (int i = 0; i < 5; ++i) {// display value of arr[i]cout << *(arr + i) << endl ;}return 0;
}

其中的cin >> *(arr + i) ;等同于cin >> arr[i];;

请注意,我们没有声明单独的指针变量,而是使用数组名称 arr 作为指针表示法。
我们已经知道,数组名 arr 指向数组的第一个元素。因此,我们可以将 arr 视为一个指针。


引用

基本概念

我们使用&符号来创建引用。例如:

string& ref_city = city;
#include <iostream>using namespace std;int main() {string city = "Paris";// create a reference to the variablestring& ref_city = city;// display the variablecout << "Variable Value: " << city << endl;cout << "Reference Value: " << ref_city << endl;return 0;
}

上面的代码中我们使用引用变量 ref_city 来显示变量 city 的值。


  • 通过引用修改变量

我们可以通过简单地为引用变量分配一个新值来修改变量。

int main()
{string city = "Shanghai";string& ref_city = city;ref_city = "Hk";cout <<"var:" << city <<endl;cout<<"ref: "<< ref_city<<endl;return 0;}

我们可以在创建引用时将 & 符号与数据类型或变量一起放置。但是,标准做法是将符号与数据类型一起使用。例如:

// create a variable
string city = "Paris";// valid but not a standard practice
string &ref_city = city;// valid and a standard practice
sring& ref_city = city;

一旦我们创建了对变量的引用,就无法将其更改为引用另一个变量。例如:

int main() {string city1 = "Paris";// create a reference to the variablestring& ref_city = city1;// display the variablecout << "city1 = " << city1 << endl;cout << "ref_city = " << ref_city << endl;string city2 = "New York";// trying to modify the ref_city reference variable to refer to city2// but it assigns the value of city2 to the variable city1ref_city = city2;// display the variablescout << endl << "city1 = " << city1 << endl;cout << "city2 = " << city2 << endl;cout << "ref_city = " << ref_city << endl;return 0;
}

注意,虽然这里最后引用ref_city输出的是新的值New York,但他指向的始终还是开始的那个city1,只是引用的值变为了新的New York

引用传递

在 C++ 函数教程中,我们学习了如何将参数传递给函数。使用的这种方法称为按值传递,因为传递的是实际值。
但是,还有另一种传递参数的方法,称为引用传递
引用传递是一种在函数中传递参数的方法,其中将实际参数的引用而不是它们的值传递给函数。

// function that takes value as parametervoid func1(int num_val) {// code
}// function that takes reference as parameter
// notice the & before the parameter
void func2(int& num_ref) {// code
}int main() {int num = 5;// pass by valuefunc1(num);// pass by referencefunc2(num);return 0;
}

注意 void func2(int& num_ref) 中的 &。这表示我们使用变量的引用作为参数。
因此,当我们通过传递变量 num 作为参数来调用 main() 中的func2()函数时,我们实际上传递的是 num 变量的引用而不是值 5

void swap(int &n1,int &n2){int temp;temp = n1;n1 = n2;n2 = temp;}
int main() {int a {1},b {2};cout<<"Before swaping" << endl;cout<<"a = "<< a <<endl;cout<<"b = "<< b << endl;swap(a,b);cout<<"\nAfter swaping" << endl;cout<<"a = "<< a <<endl;cout<<"b = "<< b << endl;return 0;
}

在这个程序中,我们将变量 a 和 b 传递给 swap() 函数。请注意函数的定义、其中的参数我们使用的是引用类型;

因此,编译器可以识别出传递给函数参数的不是实际值,而是变量的引用。
swap() 函数中,函数参数 n1 n2 分别指向与变量 a b 相同的值。因此,交换是在实际值上进行的。


常量引用

当变量的值不需要更改时,我们可以将它们作为常量引用传递。
我们来看一个例子:

#include <iostream>
using namespace std;// function to add two numbers 
// using const references
int add(const int& num1, const int& num2) {return num1 + num2;
}int main() {int number1, number2;// take inputcout << "Enter the first number: ";cin >> number1;cout << "Enter the second number: ";cin >> number2;// call add functionint sum = add(number1, number2);// displaying the resultcout << "The sum of " << number1 << " and " << number2 << " is " << sum << endl;return 0;
}

在这里,我们使用 const 关键字通过 const 引用传递值。
使用 const 引用可防止函数内的值发生更改。例如,让我们尝试使用 const 引用来交换两个数字。

#include <iostream>
using namespace std;// function definition to swap values
// using const references
void swap(const int& n1,const int& n2) {int temp;temp = n1;n1 = n2;n2 = temp;
}int main() {// initialize variablesint a = 1, b = 2;cout << "Before swapping" << endl;cout << "a = " << a << endl;cout << "b = " << b << endl;// call function to swap numbersswap(a, b);cout << "\nAfter swapping" << endl;cout << "a = " << a << endl;cout << "b = " << b << endl;return 0;
}

这个程序运行是会报错的,因为对于参数,我们使用了const进行修饰

image-20240625172322268

对于这个交换函数,我们使用指针传递参数也是可以实现的,但是不建议使用这种方式:

#include <iostream>
using namespace std;// function prototype with pointers as parameters
void swap(int*, int*);int main() {// initialize variablesint a = 1, b = 2;cout << "Before swapping" << endl;cout << "a = " << a << endl;cout << "b = " << b << endl;// call function by passing variable addressesswap(&a, &b);cout << "\nAfter swapping" << endl;cout << "a = " << a << endl;cout << "b = " << b << endl;return 0;
}// function definition to swap numbers
void swap(int* n1, int* n2) {int temp;temp = *n1;*n1 = *n2;*n2 = temp;
}
  • *n1*n2 分别给出存储在地址 n1 和 n2 处的值。*
  • 由于n1n2包含a和b的地址,因此对*n1*n2进行任何操作都会改变ab的实际值。因此,当我们在 main() 函数中打印 a b 的值时,这些值会发生变化,也就完成了交换;

使用引用而不是指针通常更容易且不易出错,因为它不涉及直接的指针操作。
指针只能用于在特别需要指针的上下文中或与 C 库交互时传递参数。


内存管理

  • C++ 允许我们在运行时动态分配内存。这就是所谓的动态内存分配。
  • 在 Java 和 Python 等其他编程语言中,编译器会自动管理分配给变量的内存。但 C++ 并非如此。
  • 在 C++ 中,当我们不再使用变量时,需要手动取消分配动态分配的内存。
  • 我们可以使用 new 和 delete 操作符分别动态分配和取消分配内存。

new关键字

我们可以使用新表达式在运行时分配内存。例如

int* point_var;
point_var = new int;
*point_var = 45;

在这里,我们使用 new 表达式为一个 int 变量动态分配了内存。
请注意,我们使用了指针 point_var 来动态分配内存。这是因为 new 表达式返回的是内存位置的地址。
我们还可以在相同的步骤中分配内存并初始化值:

int* point_var = new int{45};

使用此语法可以避免未初始化的指针。取消引用时,未初始化的指针可能会导致未定义的行为。所以这是首选语法。
使用新表达式的语法是:

data_type* pointer_variable = new data_type{value};

delete关键字

一旦我们不再需要使用动态声明的变量,就可以取消分配该变量占用的内存。
为此,我们可以使用delete关键字。它将内存归还给操作系统。这就是所谓的内存去分配。
删除表达式的语法如下:

int* point_var = new int{45};// print the value stored in memory
cout << *point_var; // deallocate the memory
delete point_var;// set pointer to nullptr
point_var = nullptr;

在这里,我们使用指针 point_var 为一个 int 变量动态分配了内存。
打印完 point_var 的内容后,我们使用 delete 删除了内存。
一个好的做法是,在取消分配内存后将指针设置为 nullptr,以避免在指针被取消引用时出现未定义的行为。

不正确地删除内存会造成内存泄漏,进而导致程序消耗大量内存。正确使用删除表达式对于防止内存泄漏和确保高效内存管理至关重要。

下面再看两个例子,加深理解:

  • ex1
#include <iostream>
using namespace std;int main() {// dynamically allocate memoryint* point_int = new int{45};float* point_float = new float{45.45f};cout << *point_int << endl;cout << *point_float << endl;// deallocate the memory// set pointers to nullptrdelete point_int;delete point_float;return 0;
}

在这个程序中,我们为两个 int 和 float 类型的变量动态分配了内存。在为它们赋值并打印后,我们最后使用删除表达式去分配内存。

动态内存分配可以提高内存管理的效率,尤其是对于数组而言,很多时候我们可能要到运行时才能知道数组的大小。

  • ex2
#include <iostream>
using namespace std;int main() {int num;cout << "Enter total number of students: ";cin >> num;float* ptr;// memory allocation of num number of floatsptr = new float[num];cout << "Enter GPA of students." << endl;for (int i = 0; i < num; ++i) {cout << "Student" << i + 1 << ": ";cin >> *(ptr + i);}cout << "\nDisplaying GPA of students." << endl;for (int i = 0; i < num; ++i) {cout << "Student" << i + 1 << ": " << *(ptr + i) << endl;}// ptr memory is releaseddelete[] ptr;ptr = nullptr;return 0;
}

在这个程序中,我们要求用户输入学生人数,并将其存储在 num 变量中。然后,我们使用 new 为浮点数组动态分配了内存。
我们使用指针符号将数据输入数组(随后打印数据)。在不再需要数组后,我们使用代码去分配数组内存:

注意删除后的[]。我们使用方括号 [] 来表示内存的删除是删除一个数组。

  • ex3
#include <iostream>
using namespace std;class Student {private:int age;public:// constructor initializes age to 12Student() : age(12) {}void get_age() {cout << "Age = " << age << endl;}
};int main() {// dynamically declare student objectStudent* ptr = new Student();// call get_age() functionptr->get_age();// ptr memory is releaseddelete ptr;return 0;
}

在这个程序中,我们创建了一个 Student 类,该类有一个私有变量 age
我们在默认构造函数 Student() 中将 age 初始化为 12,并使用函数 get_age() 打印了它的值。
main() 中,我们使用 new 表达式创建了一个 Student 对象,并使用指针 ptr 指向其地址。
对象创建后,Student() 构造函数将年龄初始化为 12 岁。


为什么要使用动态内存分配?

动态内存分配有几个优点,例如:

  • 灵活性: 动态内存分配允许我们在运行时根据需要分配内存。当编译时不知道数据结构的大小,或在程序执行过程中数据结构的大小发生变化时,这种灵活性就非常有用。
  • 数据结构: 数据结构(如链表、树、图和可调整大小的数组(C++ 中的向量))通常需要动态分配内存,以容纳不同数量的数据。
  • 资源管理: 我们可以在需要时分配内存,并在不再需要时取消分配。这样可以提高资源利用率。
  • 动态数组: 在 C++ 等语言中,静态数组的大小是在编译时确定的。动态内存分配允许我们创建数组,其大小可在运行时确定。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/35163.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

北大医院副院长李建平:用AI解决临床心肌缺血预测的难点、卡点和痛点

2024年6月14日&#xff0c;第六届北京智源大会在中关村展示中心开幕&#xff0c;海内外的专家学者围绕人工智能关键技术路径和应用场景&#xff0c;展开了精彩演讲与尖峰对话。在「智慧医疗和生物系统&#xff1a;影像、功能与仿真」论坛上&#xff0c;北京大学第一医院副院长、…

java复习宝典,jdbc与mysql数据库

一.java 1.面向对象知识 (1)类和对象 类&#xff1a;若干具有相同属性和行为的对象的群体或者抽象&#xff0c;类是创建对象的模板&#xff0c;由属性和行为两部分组成。 类是对象的概括或者抽象&#xff0c;对象是类的实例化。 举例&#xff1a;例如车有很多类型&#xf…

计算机系统基础知识(下)

嵌入式系统以及软件 嵌入式系统是为了特定应用而专门构建且将信息处理过程和物理过程紧密结合为一体的专用计算机系统&#xff0c;这个系统目前以涵盖军事&#xff0c;自动化&#xff0c;医疗&#xff0c;通信&#xff0c;工业控制&#xff0c;交通运输等各个应用领域&#xff…

【Matlab 六自由度机器人】机器人动力学之推导拉格朗日方程(附MATLAB机器人动力学拉格朗日方程推导代码)

【Matlab 六自由度机器人】机器人动力学概述 近期更新前言正文一、拉格朗日方程的推导1. 单自由度系统2. 单连杆机械臂系统3. 双连杆机械臂系统 二、MATLAB实例推导1. 机器人模型的建立2. 动力学代码 总结参考文献 近期更新 【汇总】 【Matlab 六自由度机器人】系列文章汇总 …

JVM专题十:JVM中的垃圾回收机制

在JVM专题九&#xff1a;JVM分代知识点梳理中&#xff0c;我们主要介绍了JVM为什么采用分代算法&#xff0c;以及相关的概念&#xff0c;本篇我们将详细拆分各个算法。 垃圾回收的概念 垃圾回收&#xff08;Garbage Collection&#xff0c;GC&#xff09;确实是计算机编程中的…

【自然语言处理系列】探索NLP:使用Spacy进行分词、分句、词性标注和命名实体识别,并以《傲慢与偏见》与全球恐怖活动两个实例文本进行分析

本文深入探讨了scaPy库在文本分析和数据可视化方面的应用。首先&#xff0c;我们通过简单的文本处理任务&#xff0c;如分词和分句&#xff0c;来展示scaPy的基本功能。接着&#xff0c;我们利用scaPy的命名实体识别和词性标注功能&#xff0c;分析了Jane Austen的经典小说《傲…

discuz插件之优雅草超级列表互动增强v1.2版本更新

https://doc.youyacao.com/9/2142 v1.2更新 discuz插件之优雅草超级列表互动增强v1.2版本更新 [title]20220617 v1.2发布[/title] 增加了对php8的支持 增加了 对discuz3.5的支持

RocketMQ源码学习笔记:Broker启动流程

这是本人学习的总结&#xff0c;主要学习资料如下 马士兵教育rocketMq官方文档 目录 1、Broker启动流程2、一些重要的类2.1、MappedFile2.2、MessgeStore2.3、MessageStore的加载启动流程 3、技术亮点3.1、 内存映射3.1.1、简介3.1.2、源码 1、Broker启动流程 Broker启动流程…

RabbitMQ中lazyqueue队列

lazyqueue队列非常强悍 springboot注解方式开启 // 使用注解的方式lazy.queue队列模式 非常GoodRabbitListener(queuesToDeclare Queue(name "lazy.queue",durable "true",arguments Argument(name "x-queue-mode",value "lazy&…

3.蓝牙模块HC-08

目录 一.简介​编辑 二.主要参数 三.模块引脚说明 四、LED指示灯状态 五.AT指令 5.1AT指令重点 5.2 AT指令注意点 5.3 AT指令集 六.AT常用指令 6.1 测试指令 AT 6.2 查询当前参数ATRX 6.3设置主从模式 ATROLE 6.4设置蓝牙模式 ATNAME 6.5 设置波特率 …

YOLOv5改进(八)--引入Soft-NMS非极大值抑制

文章目录 1、前言2、各类NMS代码实现2.1、general.py 3、各类NMS实现3.1、Soft-NMS3.2、GIoU-NMS3.3、DIoU-NMS3.4、CIoU-NMS3.5、EIoU-NMS 4、目标检测系列文章 1、前言 目前yolov5使用的是NMS进行极大值抑制&#xff0c;本篇文章是要将各类NMS添加到yolov5中&#xff0c;同时…

6.25作业

1.整理思维导图 2.终端输入两个数&#xff0c;判断两数是否相等&#xff0c;如果不相等&#xff0c;判断大小关系 #!/bin/bash read num1 read num2 if [ $num1 -eq $num2 ] then echo num1num2 elif [ $num1 -gt $num2 ] then echo "num1>num2" else echo &quo…

200.回溯算法:子集||(力扣)

class Solution { public:vector<int> res; // 当前子集vector<vector<int>> result; // 存储所有子集void backtracing(vector<int>& nums, int index, vector<bool>& used) {result.push_back(res); // 将当前…

【嵌入式Linux】<总览> 进程间通信(更新中)

文章目录 前言 一、管道 1. 概念 2. 匿名管道 3. 有名管道 二、内存映射区 1. 概念 2. mmap函数 3. 进程间通信&#xff08;有血缘关系&#xff09; 4. 进程间通信&#xff08;没有血缘关系&#xff09; 5. 拷贝文件 前言 在文章【嵌入式Linux】&#xff1c;总览&a…

浏览器断点调试(用图说话)

浏览器断点调试&#xff08;用图说话&#xff09; 1、开发者工具2、添加断点3、查看变量值 浏览器断点调试 有时候我们需要在浏览器中查看 html页面的js中的变量值。1、开发者工具 打开浏览器的开发者工具 按F12 &#xff0c;没反应的话按FnF12 2、添加断点 3、查看变量值

清理占道经营商贩自砸西瓜?智慧城管AI视频方案助力城市街道管理

一、背景分析 近日有新闻报道&#xff0c;在山西太原&#xff0c;城管凌晨3时许查处商贩占道经营&#xff0c;商贩将西瓜砸碎一地&#xff0c;引起热议。据悉&#xff0c;事件发生的五龙口街系当地主要街道&#xff0c;来往车辆众多。该商贩长期在该地段占道经营&#xff0c;影…

昇思25天学习打卡营第2天|快速入门

快速入门 操作步骤1.引入依赖包2.下载Mnist数据集3.划分训练集和测试集4.数据预处理5.网络构建6.模型训练7.保存模型8.加载模型9.模型预测 今天通过昇思大模型平台AI实验室提供的在线Jupyter工具&#xff0c;快速入门MindSpore。 目标&#xff1a;通过MindSpore的API快速实现一…

云计算 | 期末梳理(下)

1.模运算 2. 拓展欧几里得算法 3.扩散和混淆、攻击的分类 香农的贡献:定义了理论安全性,提出扩散和混淆原则,奠定了密码学的理论基础。扩散:将每一位明文尽可能地散布到多个输出密文中去,以更隐蔽明文数字的统计特性。混淆:使密文的统计特性与明文密钥之间的关系尽量复杂…

深入解析直播带货系统源码:短视频商城APP开发全攻略

本篇文章&#xff0c;小编将深入解析直播带货系统的源码&#xff0c;并为开发短视频商城APP提供全攻略&#xff0c;助力开发者打造高效、稳定的带货平台。 一、直播带货系统概述 直播带货系统主要由直播模块、商品管理模块、订单处理模块、用户管理模块、以及支付模块等组成。…

Ubuntu20.04使用Samba

目录 一、Samba介绍 Samba 的主要功能 二、启动samba 三、主机操作 四、Ubuntu与windows系统中文件互联 五、修改samba路径 一、Samba介绍 Samba 是一个开源软件套件&#xff0c;用于在 Linux 和 Unix 系统上实现 SMB&#xff08;Server Message Block&#xff09;协议…