【C++】C++入门2.0

各位读者老爷好,本鼠最近浅学了一点C++的入门知识!利用本博客作为笔记的同时也希望得到各位大佬的垂阅!

目录

1. 引用

1.1.引用的概念

1.2.引用的特性

1.3.引用的使用场景 

 1.4.引用的易错点

1.5.引用的优势

1.6.引用和指针

2.内联函数

2.1.内联函数的概念

2.2.内联函数的特性

3. auto关键字(C++11)

3.1.auto的概念

 3.2.auto的使用细则

4.基于范围的for循环(C++11)

4.1.范围for循环的语法

4.2.范围for的使用条件

5.指针空值nullptr(C++11)


1. 引用

1.1.引用的概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空 间,它和它引用的变量共用同一块内存空间。

“取别名”的方法:类型& 引用变量名(对象名) = 引用实体。如下例所示:

#include<stdio.h>
int main()
{
	int a = 10;
	int& ra = a;//引用,ra就是a的别名
	printf("%p\n", &a);
	printf("%p\n", &ra);
	return 0;
}

 我们看到ra和a的内存空间确实是同一块。

注意:引用类型必须和引用实体是同种类型的。如上,变量a和ra的类型都是int。

1.2.引用的特性

1.引用在定义时必须初始化,就是必须说清楚是谁的别名。

2.一个变量可以有多个引用,就是说一个变量可以取多个别名。

3.引用一旦引用一个实体,再不能引用其他实体,就是说引用不能改变指向。

#include<iostream>
using namespace std;
int main()
{
	int a = 10;
	int b = 20;
	//int& ra; 这条语句在编译时会出错,因为引用在定义时没有初始化
	int& ra = a;
	int& rra = a;
	rra = b;//这里是将b赋值给a的别名rra,可不是改变rra的指向
	cout << rra << endl;
	cout << &a << endl;
	cout << &ra << endl;
	cout << &rra ;
	return 0;
}

 

1.3.引用的使用场景 


1.做参数

举个例子:

#include<iostream>
using namespace std;
void swap(int& left, int& right)
{
	int tmp = left;
	left = right;
	right = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	swap(a, b);
	cout << "a=" << a << endl;
	cout << "b=" << b;
	return 0;
}

用引用的方式接受实参,这里left就是a的别名,right就是b的别名,通过别名直接进行交换。就不用通过地址解引用间接来进行交换了。 

  


2.做返回值

 举个例子:

#include<iostream>
using namespace std;
int& Func()
{
	static int n = 10;
	n++;
	return n;
}
int main()
{
	cout << Func() ;
	return 0;
}

这里Func函数的返回值是通过引用返回的哦,返回的是n的别名。

 


 1.4.引用的易错点


1.明确概念,举个例子:

#include<iostream>
using namespace std;
int& Func()
{
	static int n = 10;
	return n;
}
int main()
{
	int& ret = Func();
	int tmp = Func();
	return 0;
}

我们都知道函数Func返回的是n的别名。那么ret和tmp分别是什么?

其实ret是n别名的别名;tmp是将n的别名的值赋给tmp。我们可以验证:

通过调试我们可以看到ret和n是同一块内存空间,tmp的空内存间却与它们不同:

再看:

#include<iostream>
using namespace std;
int& Func()
{
	static int n = 10;
	return n;
}
int main()
{
	int& ret = Func();
	int tmp = Func();
	ret++;
	cout << ret << endl;
	cout << tmp;
	return 0;
}

 

 我们看到tmp的值确实是10。而且ret++不影响tmp,因为tmp不是n的别名,也不是ret的别名,更不是n的别名的别名,所以ret++不会影响tmp。


2.如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用 引用返回,如果已经还给系统了,则必须使用传值返回。为什么这么说,我们看一个例子:

#include<iostream>
using namespace std;
int& Add(int a, int b)
{
	int c = a + b;
	return c;
}
int main()
{
	int& ret = Add(1, 2);
	Add(3, 4);
	cout << "Add(1, 2) is :" << ret << endl;
	return 0;
}

先看结果也许会出乎你的意料:

 

结果是7的原因:我们看到Add函数返回值是c的别名。但是局部变量c出了Add函数作用域之后局部变量c的内存空间使用权已经还给操作系统了,那么局部变量c的内存空间存储的数据是什么就不好说了,这是返回c的别名就是一个野引用。我们看主函数:ret是c别名的别名,但是c的别名是一个野引用。但再次调用Add函数时(Add(3,4);),系统复用了局部变量c的内存空间,导致局部变量c的内存空间存储的值时7.所以打印出来的ret是7。

解决上面问题的方法就是传值返回,而不是使用引用返回,如上代码写法是错误的!


1.5.引用的优势

作为参数或者返回类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用作为参数或者返回类型,效率是非常低下的,尤其是当参数或者返回类型非常大时,效率就更低。


1.引用做参数效率的提高

#include <time.h>
#include<iostream>
using namespace std;
struct A 
{ 
	int a[10000];
};
void TestFunc1(A a)
{

}
void TestFunc2(A& a)
{

}
int main()
{
	A a;

	// 以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc1(a);
	size_t end1 = clock();

	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc2(a);
	size_t end2 = clock();

	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 ;
	return 0;
}

在Debug版本下结果:

 


 2.引用做返回值效率的提高

#include <time.h>
#include<iostream>
using namespace std;
struct A
{ 
	int a[10000]; 
};
A a;
// 值返回
A TestFunc1() 
{ 
	return a;
}
// 引用返回
A& TestFunc2() 
{ 
	return a; 
}
int main()
{
	// 以值作为函数的返回值类型
	size_t begin1 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc1();
	size_t end1 = clock();

	// 以引用作为函数的返回值类型
	size_t begin2 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc2();
	size_t end2 = clock();

	// 计算两个函数运算完成之后的时间
	cout << "TestFunc1 time:" << end1 - begin1 << endl;
	cout << "TestFunc2 time:" << end2 - begin2 ;
	return 0;
}

在Debug版本下结果:

 


1.6.引用和指针

引用和指针的功能是类似的、重叠的。C++的引用是对指针使用比较复杂的场景进行一些替换,让代码更加易懂,但是引用不能完全替代指针,因为:引用一旦引用一个实体,再不能引用其他实体,就是说引用不能改变指向。

引用和指针的区别:

1.在语法层面:

  • 引用是别名,不开空间;指针是地址,需要开空间存地址。(要注意语法层面来说引用不开空间,但是在底层其实开了空间,因为引用的语法含义和底层实现是背离的,引用底层是用指针实现的)
  • 引用定义后必须初始化,指针可以初始化也可以不初始化。
  • 引用一旦引用一个实体,再不能引用其他实体,就是说引用不能改变指向,但指针可以。
  • 引用相对更安全。没有空引用,但有空指针;容易出现野指针,不容易出现野引用。

2.在底层:在汇编层面上,没有引用,都是指针,引用编译后也转换成指针了。


学完引用,看看能不能看明白这个代码:

typedef struct Node
{
	struct Node* next;
	struct Node* prev;
	int val;
}Node, * PNode;
void PushBack(PNode& phead, int x)
{
	//…………
}
int main()
{
	PNode plist = nullptr;
	PushBack(plist, 10);
	return 0;
}

首先:struct Node被typedef成Node,struct Node*被typedef成PNode。 

主函数:定义了一个类型是PNode的指针变量plist,并初始化成空指针。将plist和10作为实参传入PushBack。

PushBack函数:phead是plist的别名,phead类型是PNode(也是struct Node*)。


2.内联函数

引子:如果有一个函数要频繁调用100w次,那么需要建立100w次栈帧。如何解决这个问题呢?


C语言给出的答案就是宏。说起宏,如何用宏实现下面代码?

int Add(int a, int b)
{
	return a + b;
}

答案如下:

#define Add(a,b) ((a)+(b))

 用宏实现函数功能要注意下面几点:

  • 宏是一种替换:宏在预处理阶段进行了替换。
  • 宏不是函数,没有返回值和参数。
  • 必须用括号控制好运算的优先级
  • 宏定义在行末不加分号。

宏的优缺点:

 优点: 1.增强代码的复用性。 2.提高性能。

缺点: 1.不方便调试宏。(因为预编译阶段进行了替换) 2.导致代码可读性差,可维护性差,容易误用。 3.没有类型安全的检查 。4.语法复杂,不好控制。


而C++给出的答案是内联函数,可以避免宏的缺陷。

2.1.内联函数的概念

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

像这样:

inline int Add(int a, int b)
{
	return a + b;
}
int main()
{
	int sum = Add(1, 2);
	return 0;
}

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

既然C++有内联函数的概念,我们是不是将所有函数都加上inline,这样不就没有栈帧的消耗了吗?肯定不行,因为:

2.2.内联函数的特性

1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会 用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运 行效率。

2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。就是说内联说明只是向编译器发出一个请求,编译器可以选择忽略这个请求。

3.内联函数声明和定义不可分离。分离会导致链接错误。因为inline被展开,就没有函数地址 了,链接就会找不到。

3. auto关键字(C++11)

3.1.auto的概念

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

说简单点,auto作用就是自动推导变量类型的,如:

#include<iostream>
using namespace std;
int main()
{
	double a = 13.14;
	auto b = a;
	auto c = 1;
	auto d = 'D';
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() ;
	return 0;
}

简单介绍一下,typeid().name()可以获取变量类型,用法如代码。我们看看结果:

 

 3.2.auto的使用细则


1.使用auto定义变量时必须对其进行初始化

#include<iostream>
using namespace std;
int main()
{
	auto a;//error C3531: “a”: 类型包含“auto”的符号必须具有初始值设定项
	return 0;
}

不初始化无法通过编译。


2.auto与指针和引用结合起来使用 

用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&

#include<iostream>
using namespace std;
int main()
{
	double a = 13.14;
	auto& b = a;
	auto c = &a;
	auto* d = &a;
	cout << &a << endl;
	cout << &b << endl;
	cout << c << endl;
	cout << d;
	return 0;
}

变量a和b的内存空间是同一块,也就是指针变量c或者d所指向的那块。

 


3.在同一行定义多个变量

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

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

4.auto不能作为函数的参数 

auto不能作为形参类型,因为编译器无法对a的实际类型进行推导

#include<iostream>
using namespace std;
// error C3533: 参数不能为包含“auto”的类型
int Add(auto a, auto b)
{
	return a + b;
}
int main()
{
	int i=Add(1, 2);
	return 0;
}

5.auto不能直接用来声明数组

int main()
{
	int a[] = { 1,2,3 };
	auto b[] = { 4,5,6 };//错误写法
	return 0;
}

4.基于范围的for循环(C++11)

4.1.范围for循环的语法

C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。 

意思是依次取数组元素的值赋值给范围内用于迭代的变量,自动迭代,自动判断结束。

举个栗子:

#include<iostream>
using namespace std;
int main()
{
	int array[] = { 1, 2, 3, 4, 5 };
	for (auto& e : array)
		e *= 2;

	for (auto e : array)
		cout << e << " ";
	return 0;
}

 注意:第一个for循环我们期待能改变(*2)数组元素,所以得使用引用。

 

当然与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环 。

4.2.范围for的使用条件

1. for循环迭代的范围必须是确定的

对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供 begin和end的方法,begin和end就是for循环迭代的范围。

以下代码的for循环迭代的范围就是不确定的,是错误的写法:

void TestFor(int array[])
{
    for(auto& e : array)
        cout<< e <<endl;
}

因为array实质是一个指针,而不是数组。

2.迭代的对象要实现++和==的操作。

5.指针空值nullptr(C++11)

C语言当中用NULL表示空指针,而C++用nullper表示空指针!

注意:

1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。

2.. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。

3.为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。 

感谢阅读!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

精雕细琢,B 端 UI 设计展典雅风范

精雕细琢&#xff0c;B 端 UI 设计展典雅风范

面试题:计算机网络中的七四五是什么?

面试题&#xff1a;计算机网络中的七四五是什么&#xff1f; 计算机网络中说的七四五是指&#xff1a;OSI 七层模型、TCP/IP 四层模型、OSI 与 TCP/IP 的综合五层模型 OSI 七层模型 OSI 将计算机网络分为了七层&#xff0c;每一层抽象底层的内容&#xff0c;并遵守一定的规则…

你认识nginx吗,nginx是做什么的,nginx可以做什么 --2)nginx配置

hello大家今天教大家如何用nginx实验tomcat的负载均衡&#xff0c;同理其他的也可以&#xff0c;如httpd等 首先需要准备一个nginx和tomcat包&#xff0c;这里用到的是版本号为 然后需要准备最少三台linux虚拟机&#xff0c;然后我们开始吧 1.安装tomcat 解包 tar zxf /mnt/…

数据结构(三)循环链表 约瑟夫环

文章目录 一、循环链表&#xff08;一&#xff09;概念&#xff08;二&#xff09;示意图&#xff08;三&#xff09;操作1. 创建循环链表&#xff08;1&#xff09;函数声明&#xff08;2&#xff09;注意点&#xff08;3&#xff09;代码实现 2. 插入&#xff08;头插&#x…

uni-app+php 生成微信二维码 分销海报

主要代码如下&#xff0c;可直接复制调试参数&#xff1a; //查询当前用户是否有分销海报public function user_poster(){$this->checkAuth();//查询会员信息$user $this->getUserInfoById($this->user_id);if(!empty($user[distribution_img])){$result[data] $use…

期权高频交易能做吗?期权可以频繁交易吗?

今天带你了解期权高频交易能做吗&#xff1f;期权可以频繁交易吗&#xff1f;在期权交易市场&#xff0c;大部分人都知道不能频繁交易&#xff0c;就连不少投资新手都知道频繁交易是大忌&#xff0c;是错误的&#xff0c;是应该避免的。所以是不行的。 期权高频交易能做吗&…

建WordPress主题官网模板

蓝色的中文WordPress企业模板 https://www.zhanyes.com/qiye/6305.html 暗红色WordPress律师事务所网站模板 https://www.zhanyes.com/qiye/23.html 红色大banner图WordPress外贸网站模板 https://www.zhanyes.com/waimao/27.html

js 表格添加|删除一行交互

一、需求 二、实现 <div style"margin-bottom: 55px"><form action"" method"post" enctype"multipart/form-data" id"reportForm" name"sjf" style"margin-left: 25px;margin-bottom: 50px;&quo…

笔记:Context

Context 是上下文对象&#xff0c;是 Android 常用类 Activity、Service 和 Application 都间接继承 Context &#xff0c;Context 是一个抽象类&#xff0c;内部定义了很多方法和静态常量&#xff0c;具体实现类是 ContextImpl ContextImpl 和 ContextWrapper 继承子 Context…

macOS上编译android的ffmpeg及ffmpeg.c

1 前言 前段时间介绍过使用xcode和qt creator编译调试ffmepg.c&#xff0c;运行平台是在macOS上&#xff0c;本文拟介绍下android平台如何用NDK编译链编译ffmepg库并使用。 macOS上使用qt creator编译调试ffmpeg.c macOS上将ffmpeg.c编译成Framework 大体思路&#xff1a; 其…

LTspice仿真中设置电阻随时间变化的方法

背景&#xff1a; 笔者找了很多资料都没有看到如何设置电阻、电容等参数随时间变化。但在实际模拟中&#xff0c;总会遇到需要模拟这些量的变化。故撰写此文&#xff0c;供大家参考。 除了模拟随时间变化外&#xff0c;同样的思路也可以模拟随其他变量变化 效果展示 设置电…

python3.5如何安装numpy

python3.5如何安装numpy&#xff1f;步骤如下&#xff1a; 1.首先应该将你的Python环境变量设置正确。检验是否正确的方法就是winR&#xff0c;输入cmd 。在窗口中输入python&#xff0c;应该得到如下所示的效果图&#xff1a; 可以在命令框中直接编译python。 2.安装pip&…

乡村振兴与乡村旅游创新:创新乡村旅游产品,提升旅游服务水平,打造特色乡村旅游品牌,助力美丽乡村建设

目录 一、引言 二、乡村旅游产品的创新 &#xff08;一&#xff09;挖掘乡村特色资源 &#xff08;二&#xff09;注重产品体验性 &#xff08;三&#xff09;创新旅游产品形态 三、旅游服务水平的提升 &#xff08;一&#xff09;加强基础设施建设 &#xff08;二&…

ESP32入门:1、VSCode+PlatformIO环境搭建(离线快速安装)

文章目录 背景安装vscode安装配置中文 安装Platform IO安装PIO 新建ESP32工程参考 背景 对于刚接触单片机的同学&#xff0c;使用vscodeplatformIO来学习ESP32是最方便快捷的&#xff0c;比IDF框架简单&#xff0c;且比arduino文件管理性能更好。但是platformIO安装较为麻烦&a…

《中国科技纵横》是什么级别的期刊?是正规期刊吗?能评职称吗?

问题解答&#xff1a; 问&#xff1a;《中国科技纵横》期刊是核心吗&#xff1f; 答&#xff1a;不是&#xff0c;是万方维普收录的正规期刊。 问&#xff1a;《中国科技纵横》知网收录吗&#xff1f; 答&#xff1a;知网不收录&#xff0c;万方维普收录。主管单位&#xf…

ES 生命周期管理

一 .概念 ILM定义了四个生命周期阶段&#xff1a;Hot&#xff1a;正在积极地更新和查询索引。Warm&#xff1a;不再更新索引&#xff0c;但仍在查询。cold&#xff1a;不再更新索引&#xff0c;很少查询。信息仍然需要可搜索&#xff0c;但是如果这些查询速度较慢也可以。Dele…

出吉林大学计算机考研资料适用于计专966/计学941/软专967

本人是24上岸吉大计算机专硕的考生&#xff0c;先上成绩&#xff1a; 出专业课备考过程的相关笔记资料&#xff0c;也可以提供经验分享等&#xff1a; 吉林大学计算机数据结构基础算法ADL汇总&#xff0c;适用于计专966/计学941/软专967综合整理小绿书以及期末题上重难点算法…

字符串和字符串函数(2)

前言&#xff1a; 在字符串和字符串函数&#xff08;1&#xff09;-CSDN博客中&#xff0c;已将将字符串和字符函数的使用了解&#xff0c;并且实现模拟了一些字符串的库函数。 接下来将继续深入学习字符串和字符串函数。并且模拟实现一些较为复杂的函数。 可控制字符…

gpt-4o api申请开发部署应用:一篇全面的指南

利用 GPT-4o API 开发创新应用&#xff1a;一篇全面的指南 OpenAI 的 GPT-4o 是一款集成了音频、视觉和文本处理能力的多模态人工智能模型&#xff0c;它的出现代表了人工智能领域的重大进步。在本篇文章中&#xff0c;我们将详细介绍如何通过 OpenAI API 使用 GPT-4o&#xf…

xcode开发swift允许发送http请求设置

Xcode 现在新建项目默认只支持HTTPS请求&#xff0c;认为HTTP请求不安全&#xff0c;所以不支持。但是开发环境一般都是http模式&#xff0c;所以需要单独配置才可以访问。 需要到项目的设置里面&#xff0c;点击info&#xff0c;如果没有App Transport Security Setting这一项…