c++基础学习之如何区分引用和指针

目录
  • 前言
  • 1.引用
    • 1.1引用的概念
    • 1.2引用的定义
    • 1.3引用与const
    • 1.4引用的使用场景
  • 2.指针
    • 2.1概念
    • 2.2获取对象的地址
    • 2.3利用指针访问对象
    • 2.3空指针
    • 2.4野指针
      • 2.4.1概念:
      • 2.4.2野指针的产生:
    • 2.5各个指针类型的含义
    • 2.6 void* 指针
    • 2.7指向指针的指针
    • 2.8指针与const
  • 3.指针和引用的区别
  • 总结

前言

对于我刚学c++的时候,最令我头疼的是引用和指针,老是区分不了它们,那么今天笔者将我的学习到的笔记总结出来,让大家少走一些坑。

术语:什么是对象?(对象在下面将会一直提到)

c++程序员们在很多场合都会使用对象这个名词,通常情况下,对象是指一块能存储数据并具有某种类型的内存空间。有些人仅在与类有关的场景下才使用“对象”这个词。在这里,即认为对象是具有某种数据类型的内存的空间

1.引用

1.1引用的概念

引用为对象起另外一个名字,通过声明符写成&d的形式来定义引用类型,其中d是声明的变量名。

    int i = 10;
	int& refi = i;//refi是i的另外一个名字
	int& refi2;//报错:引用必须初始化

一个对象可以有多个引用,相当于给对象起多个名字。

    //refi,refi1,refi2与i绑定在一起
    int& refi1 = i;
	int& refi2 = refi1;

定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。一旦引用无法重新绑定到另外一个对象,因此引用必须初始化。

定义引用后,对它进行所有的操作都是与之绑定的对象上进行的。

  refi = 100;//将100的值赋值给refi,即把值给i
  int j=refi;//与int j=i;是一样的

1.2引用的定义

允许在一条语句中定义多个引用,其中每个引用标识符都必须以符号&开头。

	int i = 10, int i1 = 10;//i和i1都是int型
	int& r = i, int i1 = i;//r是引用,与i进行绑定,i1是int
	int i3 = i1, & r2 = i1;//i3是int,r2是引用,与i1绑定一起
	int& r3 = i, & r4 = i;//r3和r4都是引用

除特殊情况下(下面会讲),引用需要与要绑定的对象严格匹配,而且引用只能绑定在对象上,不能与字面某个值或某个表达式的计算结果绑定在一起。

    int& refi = 10;//错误:引用类型的初始值必须是一个对象
	int i = 10;
	double& d = i;//错误:引用的类型必须是int型

1.3引用与const

把引用绑定到const对象上,我们称之为对常量的引用,与普通引用不同的是,对常量引用不能被做修改它所绑定的值。

    const int i = 10;
	const int& r1 = i;//正确:引用及其对应的对象都是常量
	const int& r2 = 200;//正确:引用及其对应的对象都是常量
	r1 = 40;//错误:r1是常量的引用,不能修改
	int& r2 = r1;//错误:r1是常量引用,只能读,r2是非常量引用,能读能修改

术语:c++程序员们经常把词组“对const的引用”简称“常量引用”。

在我们之前提到,引用的类型必须与其所引用的对象的类型一致,但是有两个例外,第一种是情况是初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换为引用类型即可,第二种是,常量引用可以绑定非const对象可以绑定。

	int i = 42;
	const int& r1 = i;//正确:允许将const int&绑定到一个普通int对象上
	const int& r2 = i * 2;//正确:r2是一个常量引用
	int& r3 = i * 2;//错误:r3是一个普通的非常量引用

而且对于常量引用还有更神奇的地方是:

	double d1 = 1.111;
	const int& r3 = d1;

上面的代码是正确的,r3是一个常量int类型的&引用,按理说应该只能绑定int类型的常量引用,单d1是一个double类型的对象 ,但编译器为了确保让r3绑定一个int类型对象,编译器自动把上述代码转换为:

	const int tmp = d1;//由double生成一个int类型的临时量
	const int& r3 = tmp;//让r3绑定这个临时量tmp

此处r3是绑定了临时量,而非d1对象,所谓的临时量对象就是当编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未命名的对象。

对const引用可以引用非const对象:

	int i = 10;
	int& r1 = i;
	const int& r2 = i;//正确:const引用可以引用非const对象,但是不能通过r2修改i
	r1 = 20;//正确:r1为非const引用,可以修改
	r2 = 30;//错误:r2为cosnt引用,不能对i进行修改

1.4引用的使用场景

1.做参数

 void Swap(int& left, int& right)
 {
    int temp = left;
    left = right;
     right = temp;
 }

int main()
{
   int a=0,b=10;
   swap(a,b);
}

引用可以用来做参数,把对象传给函数时,则函数中的参数则绑定到对应的对象中,不会产生临时变量例如:left则则是a的引用,right则是b的引用。

2.做返回值

int& Count()
{
 static int n = 0;//n存在静态区中
 n++;
 return n; //返回对象是n的引用
}

int& Add(int a, int b)
 {
 int c = a + b;

 return c;
}

int main()
{
 //这是错误的:因为c出了函数作用域后则会被销毁,则c这块空间就不存在
 //ret引用就无效。
  int& ret=ADD(1,2);
}

总结:

如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已 经还给系统了,则必须使用传值返回。

2.指针

2.1概念

指针是“指向”另外一种类型的复合类型。指针是用来存储变量的地址,本身就是一个对象,允许对指针进行赋值和拷贝,而且指针的生命周期内它可以先后指向不同的对象。而且指针无需在定义时就对它初始化,它跟其它内置类型一样,如果没有初始化,也将拥有一个不确定的值。

定义指针时类型的方法将声明符写成*d的形式,其中d是变量名,如果在一条语句中定义了几个指针变量,每个变量名之前必须有*。

    int* i1, * i2;//i1和i2都是int类型的指针
	int d, * d1;//d是int对象,d1是指向int类型的指针

2.2获取对象的地址

指针是存放对象的地址,要想获取某个对象的地址,需要使用 &(取地址操作符)

	int i = 10;
	int* pi = &i;//pi存放变量的i的地址,或者说pi是指向变量i的指针

注意:指针也是有大小的,它的大小根据在不同的平台是不同的,与指针的类型无关。

指针的大小在32位平台是4个字节,在64位平台是8个字节

在32位机器上,不管是int,char,double等内置类型,或者自定义类型,指针的大小永远都是4个字节。

除了特殊情况(下面会讲),指针的类型都要和它所指向的对象严格匹配。


	double d;
	double* pd = &d;//正确:pd是指向double对象的指针
	int* pi1 = &d;//错误:试图将double对象的地址给int类型的指针
    pi1=pd;//错误:指针pd和pi1的类型不匹配

2.3利用指针访问对象

如果指针指向了一个对象,那么想要通过指针访问对象,需要使用操作符 *(解引用操作符)

	int i = 10;
	int* pi = &i;//pi存放变量的i的地址,或者说pi是指向变量i的指针

	*pi = 20;//将i的值修改为20
	cout << *pi << endl;//输出的是i对象输出20

如上述,为*pi赋值实际上是为p所指的对象赋值。

注意:解引用仅适合那些确实指向某个对象的有效指针。

2.3空指针

空指针不指向任何对象,在使用一个指针之前,需要检查该指针是否为空指针。生成空指针的方法:

	int* p1 = nullptr;//等价于int *p1=0;
	int* p2 = 0;//直接将p2初始化为字面常量0
	//需要先#inclde cstdlib
	int* p3 = NULL;

把int变量直接赋给指针是错误的操作,即使int变量恰好等于0也不可以。

	int zero = 0;
	int* p4 = zero;//错误:不能将int变量赋值给指针

2.4野指针

2.4.1概念:

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

访问野指针,相当于去访问一个本不存在的位置上本不存在的对象。

2.4.2野指针的产生:

	int* p1;//指针没有初始化
	int arr[5] = { 0 };
	arr[5] = 11;//指针的越界访问
	int* ptr = (int*)malloc(sizeof(int));
	free(ptr);//指针指向的空间被释放

1.指针未初始化:

上诉中的p1是没有初始化的指针,它没有指向的空间,但它所占的内容将被看作一个地址值,糟糕的是,如果指针所占的空间恰好有内容,而这些内容恰好被当作一个地址,那么我们很难分清它到底是合法还是非法的。所以 建议初始化所有指针,如果不知道不清楚指针指向何处,就把它初始化为空指针。

2.指针的越界访问:

当指针指向的范围超出数组 arr 的范围时, p 就是野指针 ,当我们定义只能存储5个int类型的空间的数组,假设我们要去访问数组的第6个位置。由于第6个位置的空间没有定义出来,是未知的。所以arr[5]就是野指针。

3. 指针指向的空间释放:

当我们去malloc一块空间时,就会返回这个空间的指针去管理这块空间,当这块空间释放(销毁)掉时,但指针依然还在,指针指向的空间就未知的,就为野指针,所以我们释放一块空间时,我们需要把指针置为空,如ptr=nullptr。

2.5各个指针类型的含义

我们知道指针也有不同的类型,对于指针来说,指针的大小只和平台有关,相同平台下指针的大小都是一样的,那么指针的含义是什么?

	int n = 10;
	char* pc = (char*)&n;//pc为char类型的指针,但它指向n
	int* pi = &n;//pi为int类型的指针,指向n

	printf("%p\n", &n);//输出结果:012FF7E0
	printf("%p\n", pc);//输出结果:012FF7E0
	printf("%p\n", pc + 1);//输出结果:012FF7E1
	printf("%p\n", pi);//输出结果:012FF7E0
	printf("%p\n", pi + 1);//输出结果:012FF7E4

注意:%p是打印地址的符号,一个字节给一个对应的地址。

我们可以看出char类型的指针加1是走一个字节的距离,int类型的指针加1是走4个字节的距离。

同样的double类型的指针加1是走8个字节的距离。

总结:指针的类型决定了指针向前或者向后走一步有多大(距离)。

 //11223344是16进制数字,0x是16进制的标识符,每两个数字是一个字节
 int n = 0x11223344;
 char *pc = (char *)&n;//pc为char类型的指针,指向n
 int *pi = &n;//pi为int类型指针,指向n
 *pc = 0;   //i变为0x11223300
 *pi = 0;   //i变为0x00000000

pi和pc存放的都是n的地址,pc是char类型的指针,通过pc访问n的时候只能访问1个字节,所以*pc只改变一个字节的数值,pi是int类型的指针,通过pi访问n的时候只能访问4个字节,所以*pc改变4个字节的数值.

总结:指针的类型决定了指针能够访问对象有多少个字节的空间。

2.6 void* 指针

void*指针是一种特殊类型的指针,它可存放任意类型的指针.但我们无法确定对该地址中到底是个什么类型的对象。所以我们不能直接操作void*指针所指的对象。

	double d;
	void* pv = &d;//正确,d可以是任意类型的对象

利用void*指针能做的事比较有限,拿它和别的指针比较、作为函数的输入或输出或者赋给另外一个void*指针。

2.7指向指针的指针

指针是内存中的对象,同样指针也有地址,因此,允许把指针的地址在存放到另一个指针中。

通过*的个数可以区别指针的级别,例如 **表示指向指针的指针,***表示指向指针的指针指针。

	int i = 0;
	int* pi = &i;
	int** ppi = &pi;//ppi指向pi的指针
	*ppi;//*ppi等于pi
	**ppi = 10;//**pi等于i

2.8指针与const

指向常量的指针不能用于改变它所指向的值,要想存放常量对象的地址,只能使用指向常量的指针

	const int i = 10;
	int* pi = &i;//错误:pi是一个普通的指针
	const int* pi1 = &i;//正确:pi1是一个指向常量的指针
	*pi1 = 20;//错误:pi1指向的值不能修改

允许另一个指向常量的指针指向一个非常量对象:

	int i1 = 10;
	pi1 = &i1;//正确:但不能通过pi1修改i1的值

指针是对象,所以允许指针本身定为常量,不能被修改,而且常量指针必须初始化,把const放在*之后,则说明指针是一个常量指针,常量指针不变的是指针本身,不是指针指向的值不能改变。

	int i2 = 0;
    int i3-10;
	int* const pi2 = &i2;//pi2一直指向i2
    pi2=&i3;//错误:pi2是一个常量指针
    *pi2=12;//正确:pi2指向的值可以被修改
	const double d = 1.111;
	const double* const pd = &d;//pd是一个指向常量的常量指针
    *pd=2.22;//错误:pd指向的值不能被修改

const在*之前是修饰指针指向的值,即指针指向的值不能被修改,const在*之后是修饰指针本身,即指针不能被修改。

3.指针和引用的区别

1.指针就是一个对象,允许对指针赋值和拷贝,引用是给对象多起一个名字,不创建对象。

2.指针定义时可以不初始化,引用定义时必须初始化。

3.引用初始化完成后,引用将和它绑定的初始值一直绑定在一起。指针可以先后指向几个不同的对象。

4.有多级指针,但没有多级引用。

5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

总结

到此这篇关于c++基础学习之如何区分引用和指针的文章就介绍到这了,更多相关c++区分引用和指针内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++实现控制台随机迷宫的示例代码

    我全程使用TCHAR系列函数,亲测可以不改动代码兼容Unicode/ANSI开发环境,功能正常.大概有100行代码是来自网络的,我也做了改动,侵权请联系删除. 这个代码不能算是完美,还是会有轻微的闪屏现象,懒得再加双缓存了,大家可以自行修改.这里用的是SetConsoleCursorPosition函数和cls刷新屏幕. 好了,上代码!VS2015编译通过无警告.其他版本应该也没问题 // C++ Maze main code // Copyright (c) 2020 szx0427 #inc

  • google c++程序测试框架googletest使用教程详解

    目录 什么是googletest? googletest简介 谁在使用 GoogleTest? 相关开源项目 googletest的下载与编译 cmake gui编译 在vs2019中使用googletest GTest的一些基本概念 GTest的断言 事件机制 参数化 什么是googletest? googletest简介 ​GoogleTest 是 Google 的 C++ 测试和模拟框架,可以帮助程序员测试C++程序的结果预期,GoogleTest 的代码用cmake管理,可以使用cmak

  • 神奇的c/c++小游戏((提高你的编程兴趣)

    目录 神奇的c/c++ 神奇的c/c++ 以下代码在Dev,codeblocks,VC上都能运行 #include<stdio.h> #include<time.h> #include<stdlib.h> #include<conio.h> #include<windows.h> //下面Sleep()函数的头文件 #include<mmsystem.h> void menu() { printf(" ***********

  • 带你粗略了解c++的最大乘积

    目录 今天给大家讲最大乘积这道题目 样例 思路 代码 总结 今天给大家讲最大乘积这道题目 最大乘积 内存限制:256 MiB 时间限制:1000 ms 输入文件:maximum.in 输出文件:maximum.out 题目类型:传统 评测方式:文本比较 题目描述 给你 n n n个整数 a 1 , a 2 , a 3 , a 4... a n a1,a2,a3,a4...an a1,a2,a3,a4...an 从中任意挑选出个数字,使得乘积最大,输出乘积最大值. 输入格式 输入有多组测试数据.

  • C++ GetDlgItem用法案例详解

    GetDlgItem的用法小结 GetDlgItem用于获得指定控件ID的窗体指针,函数原型如下: HWND GetDlgItem( HWND hDlg, int nIDDlgItem ); CWnd* GetDlgItem(int nID) const; 它的使用说明中有这样一行字,**The returned pointer may be temporary and should not be stored for later use. **,那说明,它返回的指针有可能是有效的,有可能是无效

  • C++while和do-while语句求和详解

    目录 while语句求和 小结: do-while语句求和 代码如下. 总结 while语句求和 while的语言结构简洁,当符合循环条件(表达式)时,系统将执行循环体(语句).执行过程如图所示: 接下来我们将通过实例来熟悉while语句. 实例:利用while语句实现输入5名学生成绩并求和. 思路:构建循环体和循环语句. 循环体:输入数据并求和: 循环语句:不到五次时,继续循环. 代码如下. #include<iostream> using namespace std; int main()

  • CreateCompatibleDC()函数案例详解

    函数功能:该函数创建一个与指定设备兼容的内存设备上下文环境(DC). 函数原型:HDC CreateCompatibleDC(HDC hdc): 参数: hdc:现有设备上下文环境的句柄,如果该句柄为NULL,该函数创建一个与应用程序的当前显示器兼容的内存设备上下文环境. 返回值:如果成功,则返回内存设备上下文环境的句柄:如果失败,则返回值为NULL. CreateCompatibleDc函数只适用于支持光栅操作的设备,应用程序可以通过调用GetDeviceCaps函数来确定一个设备是否支持这些

  • C++ ostream用法案例详解

    概述 在 C++中,ostream表示输出流,英文"output stream"的简称.在 C++中常见的输出流对象就是标准输出流cout,很少自定义ostream的对象,更多的是直接使用cout.那么 ostream 有什么用呢,来看一个场景: class CPoint { public: CPoint(int x_,int y_):x(x_),y(y_){} int x,y; }; 这里定义了一个简单的类CPoint,如果我们实例化该类过后,想要打印对象的值: CPoint poi

  • c++基础学习之如何区分引用和指针

    目录 前言 1.引用 1.1引用的概念 1.2引用的定义 1.3引用与const 1.4引用的使用场景 2.指针 2.1概念 2.2获取对象的地址 2.3利用指针访问对象 2.3空指针 2.4野指针 2.4.1概念: 2.4.2野指针的产生: 2.5各个指针类型的含义 2.6 void* 指针 2.7指向指针的指针 2.8指针与const 3.指针和引用的区别 总结 前言 对于我刚学c++的时候,最令我头疼的是引用和指针,老是区分不了它们,那么今天笔者将我的学习到的笔记总结出来,让大家少走一些坑

  • Java基础学习之关键字和变量数据类型的那些事

    目录 一. 关键字 二. 变量 2.1 变量的定义 2.2 变量的分类 1. 按照数据类型分类 三. 字符编码 补充:变量的声明和初始化 总结 一. 关键字 Java中的关键字是由特定的单词组成,单词全为小写字母,每个都有特殊的含义,其实Java关键字也就那几十个,这个不需要背,以后都会知晓: 将以上关键字分类大概是这样的: tips: 值得注意的是goto,以前的时候还在用,现在作为Java的保留字,已经不用了,但是还是存在的,保留下来而已. Java中需要我们自定义的名字叫做标识符.比如方法

  • Python基础学习之基本数据结构详解【数字、字符串、列表、元组、集合、字典】

    本文实例讲述了Python基础学习之基本数据结构.分享给大家供大家参考,具体如下: 前言 相比于PHP,Python同样也是脚本解析语言,所以在使用Python的时候,变量和数据结构相对于编译语言来说都会简单许多,但是Python相比于PHP来说,变量类型的定义会比较严格:string->int的转换没有PHP那么方便.但这也让程序稳定性有所提升,例如和客户端交互的时候,数据库取出来的数字int和缓存取出来的数字(默认是string)需要手动进行转换(否则会有报错提示),而PHP不需要手动转换的

  • Java基础学习笔记之数组详解

    本文实例讲述了Java基础学习笔记之数组.分享给大家供大家参考,具体如下: 数组的定义于使用 1:数组的基本概念 一组相关变量的集合:在Java里面将数组定义为引用数据类型,所以数组的使用一定要牵扯到内存分配:想到了用new 关键字来处理. 2:数组的定义格式 区别: 动态初始化后数组中的每一个元素的内容都是其对应数据类型的默认值,随后可以通过下标进行数组内容的修改: 如果希望数组定义的时候就可以提供内容,则采用静态初始化的方式: a:数组的动态初始化(声明并初始化数组): 数据类型 数组名称

  • 零基础学习C/C++需要注意的地方

    谈及C/C++,功能强大.应用广泛,一旦掌握了后,若是再自学其他语言就显得轻而易举了.那为什么学C/C++的人少呢?很多人认为C/C++虽然博大精深,但也难学.其实就本人认为C/C++并非是"difficult(困难)"的,只要你能理清思路,掌握它的精髓,配合一套教材.那么学C/C++是一件非常容易且又其乐无穷的事.今天本人就与大家一起谈谈如何学习C/C++或者说学习C/C++应从哪几方面着手. 先来说说C语言. 1.入门后多看代码 在有一定基础以后一定要多看别人的代码.注意代码中的算

  • Java8函数式接口的基础学习教程

    函数式接口 1.1 函数式接口概述 函数式接口:有且仅有一个抽象方法的接口 Java中的函数式编程体现就是Lambda表达式,所以函数式接口就是可以使用与Lambda使用的接口 只有确保接口只能够有且只有一个抽象方法,Lambda才能顺利的进行推导 检测接口是不是函数式接口: @FunctionalInterface 放在接口定义的上方:如果接口是函数式接口,编译通过,反之失败. 注意: 我们自己定义函数式接口的时候,@FunctionalInterface是可选的,就算不写,只要爆炸慢煮函数式

  • Java基础学习之实参和形参

    关于变量的赋值: 如果变量是基本数据类型,此时赋值的是变量所保存的数据值. 如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值. public class ValueTransferTest { public static void main(String[] args) { System.out.println("***********基本数据类型:****************"); int m = 10; int n = m; System.out.println(&

  • Java基础学习之字符串知识总结

    一.前言 字符串是多个字符连接起来组合成的字符序列.字符串分为可变的字符串和不可变的字符串两种. (1)不可变的字符串:当字符串对象创建完毕之后,该对象的内容(上述的字符序列)是不能改变的,一旦内容改变就会创建一个新的字符串对象.Java中的String类的对象就是不可变的. (2)可变的字符串:StringBuilder类和StringBuffer类的对象就是可变的:当对象创建完毕之后,该对象的内容发生改变时不会创建新的对象,也就是说对象的内容可以发生改变,当对象的内容发生改变时,对象保持不变

  • Python基础学习之深浅拷贝问题及递归函数练习

    目录 一.深浅拷贝问题 二.递归函数练习 1. 求阶乘 2. 猴子吃桃问题 3. 打印斐波那契数列 一.深浅拷贝问题 在实际工作中,经常涉及到数据的传递,在数据传递使用过程中,可能会发生数据被修改的问题.为了防止数据被修改,就需要在传递一个副本,即使副本被修改,也不会影响原数据的使用.为了生成这个副本,就产生了拷贝.下面先了解一下几个概念:对象.可变类型.引用 Python对象:在 Python 中,对象有一种很通俗的说法是,万物皆对象.说的就是构造的任何数据类型都是一个对象,无论是数字,字符串

  • avaScript基础学习-基本的语法规则

    目录 一.运算符 二.分支语句 三.循环语句 四.异常的捕获与处理 五.js中的this关键字 六.let与const定义变量使用规则 七.js中的void链接 八.异步编程setTimeout 九.函数闭包 一.运算符 js中的+-*/运算与c语言较为相似,简写方式也类似 ++.--.+=.%=等运算规则也相同 在js中===代表绝对等于(值与类型都相同才算相同) !==(不绝对等于)值和类型有一个不相同或者都不相同为真 js中还支持三目运算符 a>b?'a大于b':'b大于a' 二.分支语句

随机推荐