【C++】新手入门指南(下)

文章目录

前言

一、引用

1.引用的概念和定义

2.引用的特性

3.引用的使用

4.const引用

5.指针和引用的关系

二、内联函数

三、nullptr

总结


前言

这篇续上篇的内容新手入门指南(上),继续带大家学习新知识。如果你感兴趣欢迎订购本专栏。


一、引用

1.引用的概念和定义

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。如:在水浒传里,林冲,外号豹子头,宋江叫"铁牛",他们都有外号,而外号的作用跟引用一样,相当于给一个变量取别名。

类型& 引用别名 = 引用对象;

复制代码
using namespace std;
int main()
{
	int a = 3;
	int& b = a;//这里&符号表示引用取别名
	int& c = a;
	int& d = b;
	cout << &a << endl;//012FF88C //这里&符号表示取地址
	cout << &b << endl;//012FF88C
	cout << &c << endl;//012FF88C
	cout << &d << endl;//012FF88C
	//每个变量地址是一样的
	return 0;
}

注:C++中为了避免引入太多的运算符,会复用C语言的一些符号,这里引用也和取地址使用了同一个符号&,大家注意区分使用。

2.引用的特性

1.引用在定义时必须初始化
引用需要初始值设定值,不然在编译时不知道引用指向哪个变量导致报错

复制代码
using namespace std;
int main()
{
//错误写法:
    int a=10;
    int& b;
//正确写法:
	int a = 10;
	int& b = a;
    return 0;
}

2.一个变量可以有多个引用

一个变量可以被多个引用指向,每个引用表示这个变量的不同别名

复制代码
using namespace std;
int main()
{
	int a = 3;
	int& b = a;
	int& c = a;
	int& d = b;
    return 0;
}

3.引用一旦引用一个实体,再不能引用其他实体

一旦引用初始化为某个变量后,就不能指向其他变量

复制代码
using namespace std;
int main()
{
	int x = 4, y = 5;
	int& z = x;
    z=y;//这里是把变量y赋值给引用z指向的变量x,不是把引用c又指向变量y
    return 0;
}

3.引用的使用

引用主要有两种用法是 引用传参 和 引用做返回值 ;优点: 减少拷贝提高效率和改变引用对象时同时改变被引用对象 。

引用做参数的时候,可以避免传值时的拷贝开销,提高效率。

复制代码
//引用传参
using namespace std;

void Swap(int& rx, int& ry)
{                           
	int tmp = rx;
	rx = ry;
	ry = tmp;
}
int main()
{
	int x = 0, y = 1;
	cout << x << " " << y << endl;
	Swap(x, y);
	cout << x << " " << y << endl;
	return 0;
}

引用做返回值的时候,在取栈顶时可以直接用引用做返回值,这样出函数作用域对象还可以用。提高效率和可以修改返回对象

复制代码
using namespace std;
typedef int STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;
void STInit(ST& rs, int n = 4)
{
	rs.a = (STDataType*)malloc(n * sizeof(STDataType));
	rs.top = 0;
	rs.capacity = n;
}

// 栈顶
void STPush(ST& rs, STDataType x)
{
	//assert(rs);
	// 满了, 扩容
	if (rs.top == rs.capacity)
	{
		printf("扩容\n");
		int newcapacity = rs.capacity == 0 ? 4 : rs.capacity * 2;
		STDataType* tmp = (STDataType*)realloc(rs.a, newcapacity *
			sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		rs.a = tmp;
		rs.capacity = newcapacity;
	}
	rs.a[rs.top] = x;
	rs.top++;
}

//取栈顶
//引用做返回值
int& STTop(ST& rs)
{
	return rs.a[rs.top - 1];
}

4.const引用

在使用引用会出现这种情况,int& rb = a*3; double d = 12.34; int& rd = d;
a*3 和12.34 保存在⼀个临时对象中, int& rd = d 发生类型转换 ,在类型转换中会产⽣临时对象存储中间值,rb和rd引用的都是临时对象。C++规定临时对象具有常性,不能修改。因此这里权限被放大,需用常引用才可以。 所以如果引用对象是需要放在临时变量就有常性的,就需要使用常引用,权限可以缩小、平移但不能放大。

复制代码
using namespace std;
int main()
{
	//权限不能放大:const可以把变量变常量,不能常量变变量
     const int a = 10;
	//int& ra = a;

	//权限可以平移:
	//const int& ra = a;

	//权限可以缩小:
	//因为const起常量的作用,所以rb虽然和b是同一个地址,但是rb不能改大只能往比b小的改
	int b = 20;
	const int& rb = b;


    //对于指针也是一样
	//权限不能放大
	const int* pa = &a;
	//int* ppa = pa;

	//权限可以缩小
	int* pb = &b;
	const int* ppb = pb;

	return 0;
}

5.指针和引用的关系

  1. 引用是一个变量的取别名不开空间,指针是存储一个变量地址,要开空间
  2. 引用在定义时必须初始化,指针建议初始化,但不是必须的。
  3. 引用在初始化时引用一个对象后,就不能再引用其他对象;而指针可以在任何时候可以改变指向对象。
  4. 引用可以直接访问指向对象,指针需要解引用才是访问指向对象。
  5. sizeof中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8个字节)
  6. 引用不存在空引用,比指针更安全,指针很容易出现空指针和野指针的问题。

二、内联函数

在C语言中,我们了解宏的定义和用法,在实现宏时会发现宏有很多弊端,比如在宏写一个加法函数 #define ADD(a, b) ((a) + (b)) 时,因为宏容易出错在语法上会出"为什么里层要加括号" 、"为什么外层要加括号"以及"为什么不能加分号"等问题。 因此C语言实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不方便调试;所以设计 inline内联函数 来代替C的宏函数。
用inline修饰的函数叫做内联函数。 编译时C++编译器会在调用的地方展开内联函数,这样调用内联 函数就需要建立栈帧了,就可以提高效率。
注:vs编译器 debug版本下面默认是不展开inline的,这样方便调试,debug版本想展开需要设置一下以下两个地方。

从汇编层上看内联函数是否展开,在VS编译器上是默认不展开的,但设置一下是会展开的。

复制代码
#include<iostream>
using namespace std;
inline int Add(int x, int y)
{
	int ret = x + y;
	ret += 1;
	ret += 1;
	return ret;
}
int main()
{
	// 可以通过汇编观察程序是否展开
	// 有call Add语句就是没有展开,没有就是展开了
	int ret = Add(1, 2);
	cout << Add(1, 2) * 5 << endl;
	return 0;
}

没设置之前不展开:

设置之后展开:

inline对于编译器而言只是一个建议,也就是说,你加了inline编译器也可以选择在调用的地方不展开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。 inline适用于频繁调用的短小函数 ,对于递归函数,代码相对多一些的函数,加上inline也会被编译器忽略。
inline不建议声明和定义分离到两个文件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错。
因为分离在两个文件,会导致预处理替换;编译时会出现语法错误;调用时,声明和定义在两个文件中,在找声明时,通过链接找到符号表,这时找不到定义,就会出现链接错误
例子如下:

复制代码
// F.h
#include <iostream>
using namespace std;
inline void f(int i);
// F.cpp
#include "F.h"
void f(int i)
{
cout << i << endl;
}
// main.cpp
#include "F.h"
int main()
{
   f(10);
   return 0;
}

会发生链接错误,编译报错:无法解析的外部符号 "void __cdecl f(int)" (?f@@YAXH@Z)

因为函数的声明和定义分离,在链接时出错从而编译报错。

三、nullptr

在C语言中,NULL本质是一个宏。
C++中NULL可能被定义为字面常量0,或者C中被定义为无类型指针(void*)的常量。
不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,本想通过f(NULL)调用指针版本的 f(int*)函数,但是由于NULL被定义成0,调用了f(int x),因此与程序的初衷相悖。f((void*)NULL); 调用会报错。

复制代码
#include<iostream>
using namespace std;
void f(int x)
{
   cout << "f(int x)" << endl;
}
void f(int* ptr)
{
   cout << "f(int* ptr)" << endl;
}
int main()
{
   f(0);
   // 本想通过f(NULL)调⽤指针版本的f(int*)函数,但是由于NULL被定义成0,调⽤了f(int
x),因此与程序的初衷相悖。
   f(NULL);
   f((int*)NULL);
   // 编译报错:error C2665: "f": 2 个重载中没有⼀个可以转换所有参数类型
   // f((void*)NULL);
   f(nullptr);
   return 0;
}

运行结果:

因此,C++11中引入nullptr,nullptr是一个特殊的关键字,nullptr是一种特殊类型的字面量,它可以转换成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,而不能被转换为整数类型。


总结

非常感谢大家阅读完这篇博客。希望这篇文章能够为您带来一些有价值的信息和启示。如果您发现有问题或者有建议,欢迎在评论区留言,我们一起交流学习。

相关推荐
2401_878624792 分钟前
opencv(双线性插值原理)
人工智能·算法·计算机视觉
doglc3 分钟前
Java 动态代理教程(JDK 动态代理)(以RPC 过程为例)
java·rpc·动态代理·jdk动态代理
小豪GO!4 分钟前
数据结构-八大排序
数据结构·算法·排序算法
烟锁池塘柳04 分钟前
【数学建模】随机森林算法详解:原理、优缺点及应用
算法·随机森林·数学建模
向哆哆22 分钟前
Java 性能优化:如何利用 APM 工具提升系统性能?
java·python·性能优化
一一Null28 分钟前
Android studio—socketIO库return与emit的使用
android·java·网络·ide·websocket·网络协议·android studio
极昆仑智慧33 分钟前
多模态知识图谱:重构大模型RAG效能新边界
人工智能·算法·语言模型·自然语言处理·知识图谱
天天扭码36 分钟前
面试官:算法题”除自身以外数组的乘积“ 我:😄 面试官:不能用除法 我:😓
前端·算法·面试
魔道不误砍柴功1 小时前
《理解 Java 泛型中的通配符:extends 与 super 的使用场景》
java·windows·python