C++入门知识(二)

news2025/1/16 19:57:16

最近太忙了,发论文写开题,有两周时间没有学习C++了,因为都是抽时间来学习,所以本篇博客也是零零散散的,接下来尽量抽时间吧

目录

六、引用

6.1 引用概念

6.2 引用特性

6.3 常引用 

6.4 使用场景

6.5 传值、传引用效率比较

6.6 指针和引用的区别(高频面试题)

七、内联函数

7.1 概念

7.2 特性

八.、auto关键字(C++11)

8.1 类型别名思考

8.2 auto简介

8.3 auto的使用细则

8.4 auto不能推导的场景

九、 基于范围的for循环(C++11)

9.1 范围for的语法

9.2 范围for的使用条件

十、 指针空值nullptr(C++11)


六、引用

6.1 引用概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。 (取别名)
void Swap(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}
int main()
{
	int a = 10;
	int b = 20;
	Swap(a, b);
	printf("%d %d\n", a, b);
	return 0;
}

如上代码,left就是a的别名, right既是b的别名,只在前面加个 &即可

如下代码,a和ra地址相同,他们使用的是同一块内存空间,所以修改一个,都就变了

ra就是给已经存在的a取了个别名

int main()
{
	int a = 10;//实体
	//给a取别名,为ra
	int& ra = a;
	cout << a << endl;
	cout << &a << endl;
	cout << &ra << endl;
	//修改ra,a也会改变,因为是同一个东西
	ra = 100;
	cout << a << endl;
	return 0;
}

注意:引用类型必须和引用实体是同种类型的

如果给上面代码加上一句: long& la = a;    那么就会报错 //error c2440:无法从Int转换为long

6.2 引用特性

  1. 引用在定义时必须初始化
2. 一个变量可以有多个引用
3. 引用一旦引用一个实体,再不能引用其他实体 
比如ra引用a了,就不用再去用ra引用b
void TestRef()
{
 int a = 10;
// 下面该条语句编译时会出错,没有初始化
 // int& ra; 
//一个变量可以多个引用,类似于一个人有多个绰号
 int& ra = a;
 int& rra = a;
 printf("%p %p %p\n", &a, &ra, &rra); 
}

6.3 常引用 

将const类型的引用称为const引用

const修饰a,说明a是常量,常量不允许修改

void TestConstRef()
{
const int a = 10;
//int& ra = a; // 该语句编译时会出错,普通类型引用不能用,不然你引用了,修改ra会改变a,冲突
const int& ra = a;


// int& b = 10; // 该语句编译时会出错,b为常量
const int& b = 10;


double d = 12.34;
//int& rd = d; // 该语句编译时会出错,类型不同
const int& rd = d;
}


如上,定义的是double   d,按道理int& rd与之类型不同,是不同通过的,但前面加上 const 就可以正常运行。为什么?

编译器发现double和int之间可以发生隐式类型转换,于是重新创建一块整形空间,将d中的整形部分放在临时空间中。因为临时空间是编译器线上开辟的,用户不知道这块空间的名字,也不知道这块空间的地址,自然临时空间中的值就不能被修改,即:临时空间具有常性


6.4 使用场景

1.为了简化代码直接给复杂的表达式取别名

struct A
{
	int a;
};

struct B
{
	A aa;
	int b;
};
struct C
{
	B bb;
	int c;
};
int main()
{
	struct C cc;
	cc.c = 1;
	cc.bb.b = 2;
	cc.bb.aa.a = 3;

	//为了简化代码,最后效果一样
	B& bb = cc.bb;
	bb.b = 2;
	bb.aa.a = 3;
	return 0;
}

2.引用作为函数的形参

这就是本次一开始的代码,在调用时,形参left是a的别名,right是b的别名

注意:如果不想通过形参修改外部的实参,可以将形参设置为const类型的引用

void Swap(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}
int main()
{
	int a = 10;
	int b = 20;
	Swap(a, b);
	printf("%d %d\n", a, b);
	return 0;
}

3.用引用作为函数的返回值

int& Add(int left, int right)
{
	int temp = left + right;
	return temp;
}
int main()
{
	int& ret = Add(1, 2);
	printf("%d\n", ret);
	printf("%d\n", ret);
	printf("%d\n", ret);
	return 0;
}

按道理,一般认为结果都是3,实际运行如下

并且也有警告: warning C4172: 返回局部变量或临时变量的地址: temp 

ret将add的返回值接收了之后,在程序中并没修改ret,但是后两次打印ret时结果发生变化,为什么?

规则:函数以引用的方式返回,一定不能返回函数栈上的空间

因为:当函数调用结束之后,栈上的空间就被回收了

如果在外部以引用的方式接收函数的返回值,外部的引用实际引用的就是一块非法的空间

正确的返回方式:返回的实体只要不随函数的结束而销毁

比如:全局变量,静态变量,引用类型的参数

6.5 传值、传引用效率比较

       以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低
#include<iostream>
#include<time.h>
using namespace std;

struct SeqList
{
	int array[1000];
	int size;
};

void TestValue(SeqList s) //传值
{

}
void TestPtr(SeqList* ps) //传地址
{}

void TestRef(SeqList& s)   //引用
{}

void TestTime(int n)
{
	SeqList s;
	//获取起始时间
	size_t beginVal = clock();
	for (int i = 0; i < n; ++i)
	{
		TestValue(s);
	}
	//获取结束的时间
	size_t endVal = clock();
	cout << "TestVal:" << endVal - beginVal << endl;

	//获取起始时间
	size_t beginPtr = clock();
	for (int i = 0; i < n; ++i)
	{
		TestPtr(&s);
	}
	size_t endPtr = clock();
	cout << "TestPtr:" << endPtr - beginPtr << endl;

	//获取起始时间
	size_t beginRef = clock();
	for (int i = 0; i < n; ++i)
	{
		TestRef(s);
	}
	size_t endRef = clock();
	cout << "TestRef:" << endRef - beginRef << endl;
}
int main()
{
	TestTime(1000000);
	return 0;
}

 通过上述代码的比较,发现传值和指针在作为传参以及返回值类型上效率相差很大

       传地址和传引用的效率差不多,而引用比指针更加安全、代码可读性高,所以一般情况下推荐传引用  。

6.6 指针和引用的区别(高频面试题)

首先进行一段代码的比较,并查看反汇编程序

 问题:

引用为别名,编译器不会给引用变量重新开辟内存空间,引用与其引用的实体共用同一份内存空间。但是发现:引用实际有空间,空间存放的是引用实体的地址,如何理解解释?

语法概念阶段:引用就是别名,编译器不会给引用变量开辟空间,方便理解

底层实现:要实现引用的技术,底层又把引用还原成指针---引用是语法层面的概念,在底层实际是没有引用的概念的,只有指针。

回到问题:指针和引用的区别

答:在底层实现上:引用和指针就是一样的,即引用在底层就是按照指针的方式实现的,因此引用实际上也是有空间的,内存存储的是其引用实体的地址。

引用和指针的不同点:

1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
2. 引用 在定义时 必须初始化 ,指针没有要求
3. 引用 在初始化时引用一个实体后,就 不能再引用其他实体 ,而指针可以在任何时候指向任何一个同类型实体
4. 没有 NULL 引用 ,但有 NULL 指针
5. sizeof 中含义不同 引用 结果为 引用类型的大小 ,但 指针 始终是 地址空间所占字节个数 (32 位平台下占4个字节 )
6. 引用自加即引用的实体增加 1 ,指针自加即指针向后偏移一个类型的大小
7. 有多级指针,但是没有多级引用
8. 访问实体方式不同, 指针需要显式解引用,引用编译器自己处理
9. 引用比指针使用起来相对更安全

                

七、内联函数

宏的优缺点?
优点:
1. 增强代码的复用性。
2. 提高性能。
缺点:
1. 不方便调试宏。(因为预编译阶段进行了替换)
2. 导致代码可读性差,可维护性差,容易误用。
 3.没有类型安全的检查 。
C++ 有哪些技术替代宏
1. 常量定义 换用 const enum
2. 短小函数定义 换用内联函数

C++对c语言中的宏进行了优化

宏常量的优势:

1.可以达到一改全改的效果,提高了程序的扩展性

2.可以提高程序的可读性

#define NUM 100
int main()
{
	int array[NUM];
	for (int i = 0; i < NUM; i++)
	{
		array[i] = i * 10;
	}
	for (int i = 0; i < NUM; i++)
	{
		cout << array[i] << " ";
	}
	cout << endl;

	return 0;
}

宏常量的缺陷:

宏常量在定义时没有类型,在预处理阶段发生的替换,也不会进行类型检测

如下代码在3.14外加了双引号,运行报错却在第五行

#define PI "3.14"
int main()
{
	double r = 2.0;
	cout << PI * r * r << endl;
	cout << PI * 2 * r << endl;
	return 0;
}

因为宏常量有缺陷,因此在C++中,使用Const定义的常量代替宏

在C++中,被const修饰的变量不在是变量,而是一个常量

在C语言中,被const修饰的变量不是常量,而是一个不能被修改的变量

在C++中使用const修饰,会在定义PI时报错,清清楚楚,不会引起麻烦。

宏函数

优点:在预处理阶段展开(展开:就是用宏体替换宏使用的位置)少了函数调用的开销

7.1 概念

inline 修饰 的函数叫做内联函数, 编译时 C++ 编译器会在 调用内联函数的地方展开 ,没有函数调用建立栈帧的开销, 内联函数提升程序运行的效率

 如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。 查看方式:

1. release 模式下,查看编译器生成的汇编代码中是否存在 call Add
2. debug 模式下,需要对编译器进行设置,否则不会展开 ( 因为 debug 模式下,编译器默认不会对代码进行优化,以下给出vs2013 的设置方式 )

 

7.2 特性

1. inline 是一种 以空间换时间 的做法,如果编译器将函数当成内联函数处理,在 编译阶段,会用函数体替 换函数调用 ,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
2. inline 对于编译器而言只是一个建议,不同编译器关于 inline 实现机制可能不同 ,一般建议:将 函数规 模较小 ( 即函数不是很长,具体没有准确的说法,取决于编译器内部实现 ) 不是递归、频繁调用 的函数采用inline 修饰,否则编译器会忽略 inline 特性
3. inline 不建议声明和定义分离,分离会导致链接错误。因为 inline 被展开,就没有函数地址了,链接就会找不到

八.、auto关键字(C++11)

8.1 类型别名思考

随着程序越来越复杂,程序中用到的类型也越来越复杂,经常体现在:
1. 类型难于拼写
2. 含义不明确导致容易出错
使用typedef给类型取别名确实可以简化代码,但是typedef有会遇到新的难题

typedef char* pstring;
int main()
{
 const pstring p1; // 编译成功还是失败?
 const pstring* p2; // 编译成功还是失败?
 return 0;
}
上例代码把pstring等价于char*,但是实际编译时第四行p1会报错,const放在pstring前后意义是不同的
在编程时,常常需要把表达式的值赋值给变量,这就要求在声明变量的时候清楚地知道表达式的类型。然而 有时候要做到这点并非那么容易

8.2 auto简介

C++11 中,标准委员会赋予了 auto 全新的含义即: auto 不再是一个存储类型指示符,而是作为一个新的类型 指示符来指示编译器, auto 声明的变量必须由编译器在编译时期推导而得

使用 auto 定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导 auto 的实际类 。因此 auto 并非是一种 类型 的声明,而是一个类型声明时的 占位符 ,编译器在编译期会将 auto 替换为 变量实际的类型
auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化

int main()
{
	int a = 10;
	double r = 2.0;

	auto a1 = 5;
	auto r1 = 10.22;
	cout <<typeid(a1).name() << endl;
	cout <<typeid(r1).name() << endl;
	return 0;
}

查看 a1,d1的类型

 

8.3 auto的使用细则

1. auto 与指针和引用结合起来使用
用auto声明指针类型时,用auto和auto*没有任何区别 但用auto声明引用类型时则必须加& ,如下代码:
int main()
{
 int x = 10;
 auto a = &x;
 auto* b = &x;
 auto& c = x;
 cout << typeid(a).name() << endl;
 cout << typeid(b).name() << endl;
 cout << typeid(c).name() << endl;
 *a = 20;
 *b = 30;
 c = 40;
 return 0;
}

如下,加不加*都是int*类型

2. 在同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对 第一个类型进行推导,然后用推导出来的类型定义其他变量

void TestAuto()
{
 auto a = 1, b = 2; 
 auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}

8.4 auto不能推导的场景

1. auto 不能作为函数的参数
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}
2. auto 不能直接用来声明数组
void TestAuto()
{
 int a[] = {1,2,3};
 auto b[] = {4,5,6};
}
3. 为了避免与 C++98 中的 auto 发生混淆, C++11 只保留了 auto 作为类型指示符的用法
4. auto 在实际中最常见的优势用法就是跟以后会讲到的 C++11 提供的新式 for 循环,还有 lambda 表达式等 进行配合使用。

九、 基于范围的for循环(C++11)

9.1 范围for的语法

C++11 中引入了基于范围的for 循环。 for 循环后的括号由冒号 分为两部分:第一部分是范围内用于迭代的变量, 第二部分则表示被迭代的范围
void TestFor()
{
 int array[] = { 1, 2, 3, 4, 5 };
 for(auto& e : array)
 e *= 2;
 
 for(auto e : array)
 cout << e << " ";
 
 return 0;
}

for(auto e: 范围):e将来是范围中的每个元素的拷贝,即不能通过e修改范围中的数据

for(auto& e: 范围):e将来是范围中每个元素的别名,即可以通过e修改范围中的元素

9.2 范围for的使用条件

1. for 循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围 ;对于类而言,应该提供 begin end 的方法,begin end 就是 for 循环迭代的范围。 注意:以下代码就有问题,因为for 的范围不确定
void TestFor(int array[])
{
 for(auto& e : array)
 cout<< e <<endl;
}
2. 迭代的对象要实现 ++ == 的操作

十、 指针空值nullptr(C++11)

在c++11之前,统一使用NULL表示空值指针,下面为表示方法

int main()
{
	//c++98
	int* pq = NULL;

	//c++11
	int* p2 = nullptr;

	return 0;
}
NULL 可能被定义为字面常量 0 ,或者被定义为无类型指针 (void*) 的常量 。不论采取何种定义,在
使用空值的指针时,都不可避免的会遇到一些麻烦,
void f(int)
{
 cout<<"f(int)"<<endl;
}
void f(int*)
{
 cout<<"f(int*)"<<endl;
}
int main()
{
 f(0);  //调用:f(int)
 f(NULL);   
 f((int*)NULL);
 return 0;
}

在  f (NULL)  中发现,并没有调用int*的版本,实际上调用的也是int的重载方法,原因是直接把NULL定位为0了。程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖

注意:
1. 在使用 nullptr 表示指针空值时,不需要包含头文件,因为 nullptr C++11 作为新关键字引入的
2. C++11 中, sizeof(nullptr) sizeof((void*)0) 所占的字节数相同。
3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用 nullptr

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

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

相关文章

并发编程(一)可见性

【并发编程三大特性】&#xff1a; 可见性 有序性 原子性&#xff08; 较复杂 &#xff09; 【线程的可见性】&#xff1a; 【一个例子认识线程的可见性】&#xff1a; import Utils.SleepHelper; import java.io.IOException;public class T01_HelloVolatile {private sta…

Go中的泛型和反射以及序列化

嗨喽,小伙伴们,好几没有更新了,最近在搞一些云原生的东西,docker , k8s 搞得我暂时迷失了方向,不过我举得搞IT吗,就是在不断尝试,搞一下当下最新的技术,不然 … GO中的泛型与继承 搞过java的都知道泛型与继承,在go中也开始搞泛型与继承了(在go1.8之后) 先看代码–>> p…

【记录】PyCharm 安装 preprocess 模块(库)|| 在 PyCharm 中安装 preprocess 失败,故而在 终端 安装

preprocess.py 针对的是处理许多 文件类型。它工作的语言包括&#xff1a;C、Python、 perl、tcl、xml、javascript、css、idl、tex、fortran、php、java、shell 脚本&#xff08;bash、csh等&#xff09;和c。预处理可以作为 命令行应用程序和作为python 模块。 目录一、在 Py…

矩阵论复习提纲

矩阵论复习提纲 第一章 矩阵相似变化 1、特征值与特征向量 A ∈ Cnxn 若存在 λ ∈ C 满足 Ax λx 则 λ 为 A 的特征值 可转换为 &#xff08;λI - A&#xff09;x 0 特征多项式 &#xff1a;det(λI - A) 特征矩阵&#xff1a; λI - A 2、相似对角化 1. 判断可对角化…

VMware Fusion 13 正式版终于来了

千呼万唤&#xff0c;经历两年之久&#xff0c;VMware终于在Fusion 13正式版中支持了Apple Silicon 版Mac&#xff0c;此次发布的Fusion是Universal版本&#xff0c;也就是一个安装包同时适配Intel Mac及Apple Silicon &#xff08;M1&#xff0c;M2&#xff09;Mac。想起我两年…

疑难杂症集合(备忘)

sshd&#xff1a;no hostkeys available 解决过程: #ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key #ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key #/usr/sbin/sshd 如果上述两个文件存在&#xff0c;仍然出现这个错误&#xff0c;那么试试 chmod 600 上述两个文件。之…

01-Python的基本概念

01-Python的基本概念 Python是一种直译式&#xff08;Interpreted&#xff09;、面向对象&#xff08;Object Oriented&#xff09;的程序语言’它拥有完整的函数库’可以协助轻松地完成许多常见的工作。 所谓的直译式语言是指’直译器&#xff08;InteIpretor&#xff09;会将…

诊断故障码(Diagnostic Trouble Code-DTC)

诊断协议那些事儿 诊断协议那些事儿专栏系列文章&#xff0c;本文将由浅入深的介绍DTC&#xff08;Diagnostic Trouble Code&#xff09;。 关联文章&#xff1a; $19服务:DTCStatusMask和statusofDTC bit 定义 19服务List 文章目录诊断协议那些事儿DTC&#xff08;Diagnos…

Python pyenv install 下载安装慢(失败)完美解决

pyenv 下载原理就是将例如 Python-3.10.3.tar.xz 这样的压缩文件下载到本地缓存文件或临时文件&#xff0c;然后解压出来使用。 由于下载速度或者网不行&#xff0c;那么就跳过 pyenv 下载&#xff0c;自己下载&#xff0c;然后放到它的缓存文件&#xff0c;这样不就行了。 1…

3.np.random

1. np.random.seed np.random.seed()函数用于生成指定随机数。 seed()被设置了之后&#xff0c;np.random.random()可以按顺序产生一组固定的数组&#xff0c;如果使用相同的seed()值&#xff0c;则每次生成的随机数都相同。 如果不设置这个值&#xff0c;那么每次生成的随机数…

[附源码]计算机毕业设计JAVA基于Java的护肤品网站

[附源码]计算机毕业设计JAVA基于Java的护肤品网站 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM my…

【MySQL进阶】表的增删改查操作(CRUD)+(SQL执行顺序)

1. 新增(复制数据)2. 查询 - 进阶2.1 聚合查询2.2 group by2.3 having2.4 联合查询2.4.1 内连接2.4.2 外连接2.4.3 自连接2.4.4 子查询2.4.5 合并查询3 SQL的执行顺序(where...)1. 新增(复制数据) 语法 -- 字段名 列名 -- 将表2的数据复制到表1中 -- 两张表的结构要一样 i…

前端知识点

1.HTML 2.CSS 3.js 4.VUE 5.vUE的基本指令 6.VUE案例 7.ELEMENT HTML 设置图片 <img src"图片地址">让图片居中显示<center><img src"图片地址" width"270" height"900"></center>有序列表 <!--有序 ty…

Spring框架技术的核心与设计思想

目录 1. Spring 是什么? 1.1 什么是容器? 1.2 什么是 IoC ? 2. 传统式开发 3. 控制(权)反转式开发 4. 理解Spring 核心 - IoC 1. Spring 是什么? Spring 的全称是 Spring Framework, 它是一种开源框架, 2002 年, Rod Jahnson 首次推出了 Spring 框雏形 interface21…

[UE笔记]客户端服务器时间同步

内容系看教程所做的笔记 时间 往返时间&#xff08;RTT, Round-Trip Time&#xff09;&#xff1a;数据从客户端通过网络发送到服务器&#xff0c;再从服务器返回到客户端所需的时间。 首先客户端应当知道服务端的当前时间。 服务器启动时间总是先于客户端的&#xff0c;客户…

【学习OpenCV4】OpenCV入门精讲(C++/Python双语教学)

大家好&#xff0c;我在CSDN开的OpenCV课程已经开课了&#xff0c;入口如下&#xff1a; OpenCV入门精讲&#xff08;C/Python双语教学&#xff09; 课程开始快一周了&#xff0c;收到了广大同学们的欢迎。 &#xff08;1&#xff09;评分很高&#xff1b; &#xff08;2&#…

从Clickhouse 到 Snowflake: 云原生

云原生Clickhouse优势概述 以Clickhouse为基础&#xff0c;借鉴Snowflake等系统的设计思路&#xff0c;打造一款高性能的云原生OLAP系统&#xff0c;为用户提供多场景下的一站式的数据分析平台。 简单、易维护&#xff1a;集群管理、统一共享分布式调度服务高可用、可扩展&am…

T292114 [传智杯 #5 练习赛] 清洁工

题目描述 有一个 n\times nnn 的地块&#xff0c;一个连续 ii 分钟没人经过的地面在第 ii 分钟会落上 ii 个单位的灰&#xff0c;有人经过时不会落灰但灰也不会清零&#xff0c;在人走后第一分钟又会落上一个单位的灰&#xff0c;以此类推。你在这个 n\times nnn 的范围内移动…

深度可分离卷积神经网络与卷积神经网络

在学习语义分割过程中&#xff0c;接触到了深度可分离卷积神经网络&#xff0c;其是对卷积神经网络在运算速度上的改进&#xff0c;具体差别如下&#xff1a; 一些轻量级的网络&#xff0c;如mobilenet中&#xff0c;会有深度可分离卷积depthwise separable convolution&#…

Xamarin.Andorid实现界面弹框

目录1、使用系统自带的样式1.1 具体实现1.2 效果2、自定义样式的实现2.1 预期效果2.2 具体实现2.3 相关知识3 代码下载4、参考在App的实际使用中&#xff0c;一定会出现弹框选择的情况。如图所示&#xff1a; 因此非常有必须学会及使用弹框的功能&#xff0c;因此本次学习Xama…