【C++初阶】第一站:C++入门基础(下)

前言: 

紧接着上两篇文章,c++入门基础(上):C++入门基础(上)

                                 c++入门基础(中):C++入门基础(中)

        继续补充完c++初阶入门基础的知识点,本章知识点包括:

        引用和指针的区别、内联函数、auto关键字(C++11)、基于范围的for循环(C++11)、指针空值nullptr(C++11)等补充知识。

目录

6.引用

6.6 引用和指针的区别

7.内联函数

7.1概念

7.2 特性

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

8.1 类型别名思考

8.2auto简介

特别注意:auto不能推导的场景

记住关键字:typeid

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

9.1 范围for的语法

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


6.引用

6.6 引用和指针的区别

来看下面代码,右击鼠标转到反汇编:

 可以看到汇编代码,大家都是一样的,解析下面的汇编代码:

dword         双字 就是四个字节 

ptr               pointer缩写 即指针

[]里的数据是一个地址值,这个地址指向一个双字型数据

比如mov eax, dword ptr [a]         把内存地址a中的双字型(32位)数据赋给eax

        lea表示"load effective address",表示对某个变量的地址进行加载,相当于取地址的操作。所以lea eax, [a]就是将a的地址赋给eax寄存器。

接着分别进行以下操作:

一样的转到反汇编:

引用和指针的不同点:(建议别背,从使用的角度区分)

1. 引用概念上定义一个变量的别名,指针存储一个变量地址。

2. 引用在定义时必须初始化,指针没有要求

3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何 一个同类型实体

4. 没有NULL引用,但有NULL指针

5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32 位平台下占4个字节)

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

7. 有多级指针,但是没有多级引用

8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理

9. 引用比指针使用起来相对更安全

7.内联函数

复习一下:实现一个ADD的宏函数

#define N 10

以下均为错误案例:

#define ADD(int x,int y) {return x+y;}
#define ADD( x, y) {return x+y;}
#define ADD( x, y) return x+y;

#define ADD(x,y) x+y;
#define ADD(x,y) x+y;
#define ADD(x,y) (x+y);//特别注意一下这一条为什么错误。

解析上面需要注意那条 : 

从以上可以简单看出宏函数的优缺点:

缺点:

1、容易出错,语法坑很多

2、不能调试

3、没有类型安全的检查

优点:

1、没有的类型的严格限制
2、针对频繁调用小函数,不需要再建立栈帧,提高了效率

引出我们的内联函数,也是不需要建立栈帧.

7.1概念

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

        如果在上述函数前增加inline关键字将其改成内联函数, 在编译期间编译器会用函数体替换函数的调用。
查看方式:
1. 在release模式下,查看编译器生成的汇编代码中是否存在 call Add
2. 在debug模式下,需要对编译器进行设置,否则不会展开 ( 因为debug模式下,编译器默认不会对代码进行优化,以下给出vs2019的设置方式 )

在debug设置下默认是不会展开的:

在使用内联函数之前,需要进行以下设置:

展开是什么意思呢,就是不用调用函数,直接把函数里的功能直接拷贝放到main函数内实现:

        而且解决了符号优先级的问题,因为函数传参,是把表达式算出结果再传进去的。

7.2 特性

        inline是一种 以空间换时间的做法,如果编译器将函数当成内联函数处理,在 编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率      
         inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建
议:将 函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、
是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。下图为
《C++prime》第五版关于inline的建议:

编译器视为内联函数

因代码膨胀,编译器不视为内联函数

 add是内联函数,func被编译器视为不是内联函数。

把func函数类冗余的代码删除:

这样就被视作为内联了。

测试代码:

#include<iostream>
#include<vector>
#include<string.h>

#define ADD(x,y) ((x)+(y))
inline int add(int x, int y)
{
	return x + y;
}
inline int func()
{
	int x1 = 0;
	int x2 = 0;
	int x3 = 0;
	int x4 = 0;

	int ret = 0;
	ret += x1;
    //以下代码全注释掉
	/*ret -= x2;
	ret -= x3;
	ret -= x3;
	ret -= x3;
	ret -= x3;
	ret -= x3;
	ret -= x3;
	ret -= x3;
	ret -= x3;
	ret -= x3;
	ret -= x3;
	ret -= x3;
	ret -= x3;
	ret -= x3;*/

	return 0;
}
int main()
{
	ADD(1, 2);
	printf("%d\n",ADD(1,2));
	printf("%d\n",ADD(1,2)*3);


	int a = 1, b = 2;
	//ADD(a | b, a & b);//(a | b + a&b)

	//int ret = add(1, 2);
	int ret = add(a | b, a & b);
	printf("%d\n",ret);

	ret = func();
	
	}

声明和定义分离

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

测试代码:

Func.h

#pragma once
#include<iostream>
using namespace std;
inline void  f(int i);

void fx();

Func.cpp

#include"Func.h"
void f(int i)
{
	cout << "f(int i)" << i << endl;
}
void fx()
{
	//既有声明,又有定义,是直接展开
	f(1);
}

 main.cpp

#include "F.h"
int main()
{
	f(1);
    fx();
	return 0;
}

以上代码执行: 

无法解析的外部符号"void_cdecl f(int)"(?f@@YAXH@Z),函数 main 中用了该符号

 说明:

        在一个源文件形成可执行程序的阶段,预编译阶段的作用其中之一就是头文件的包含,实际上是展开

所以说预编译之后,Func.cpp的代码其实是这个样子的:

#include <iostream>
using namespace std;
inline void f(int i);
void f(int i)
{
	cout << i << endl;
}

void fx()
{
    f(1);
}

编译器一看,f()函数是一个内联(inline),在调用的地方直接展开了,也就不会建立函数栈帧(不会有那一堆的汇编指令),所以就没有函数地址了,也就不会进入符号表。

即Func.cpp的符号表中,f()函数的地址是无效的。

在链接阶段,在这个main.cpp内调用f()函数,就去查这个符号表,发现地址是无效的,就会报链接型错误。

(编译链接知识点欠缺的,传送🚪:编译链接基础知识(上))

图解:

改正:

如何避免这个链接型错误?

        那就让内联函数声明和定义不分离:这样的话在预编译阶段,就把Func.h内的头文件展开了,这样的话呢,在编译阶段就展开了,那么在main.cpp的符号表里面就有了f()函数的地址,在链接阶段通过main函数里面的f()函数调用,一查符号表就找到了f()函数。

 Func.h

#pragma once
#include<iostream>
using namespace std;

inline void f(int i)
{
	cout << "f(int i)" << i << endl;
}
void fx();

Func.cpp

#include"Func.h"

void fx()
{
	//既有声明,又有定义,直接展开
	f(1);
}

main.cpp

#include"Func.h"
#include<stdio.h>
int main()
{
	//只有声明
	cout << "内联函数:" << endl;
	f(1);
	cout << "调用内联函数的函数:" << endl;
	fx();
}

以上代码执行: 

C++有哪些技术替代宏

1. 常量定义 换用const enum

2. 短小函数定义 换用内联函数

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

8.1 类型别名思考

随着程序越来越复杂,程序中用到的类型也越来越复杂,经常体现在:

1. 类型难于拼写

2. 含义不明确导致容易出错

#include <string>
#include <map>
int main()
{
	std::map<std::string, std::string> m{ { "apple", "苹果" }, { "orange",
										"橙子" }, {"pear","梨"} };
	std::map<std::string, std::string>::iterator it = m.begin();
	while (it != m.end())
	{
		//....
	}
	return 0;
}

std::map<std::string, std::string>::iterator是一个类型,但是该类型太长了,特别容易写错。

这时候想到咱们以前学过typdef:可以通过typedef给类型取别名,比如

#include <string>
#include <map>
typedef std::map<std::string, std::string> map;
int main()
{
	map m{ { "apple", "苹果" },{ "orange", "橙子" }, {"pear","梨"} };

	map::iterator it = m.begin();
	while (it != m.end())
	{
		//....
	}
	return 0;
}

使用typedef给类型取别名确实可以简化代码,但是typedef有会遇到新的难题:

typedef char* pstring;
int main()
{
     const pstring p1;    // 编译成功还是失败?
     const pstring* p2;   // 编译成功还是失败?
     return 0;
}

        编译失败,因为const pstring p1;这行代码定义了一个常量指针,但没有初始化,编译器无法确定它指向的具体地址;而const pstring* p2;这行代码定义了一个指向常量指针的指针,但没有指定指针指向的类型,也无法确定它指向的具体地址。

        在编程时,常常需要把表达式的值赋值给变量,这就要求在声明变量的时候清楚地知道表达式的 类型。然而有时候要做到这点并非那么容易,因此C++11给auto赋予了新的含义

8.2auto简介

在早期C/C++中auto的含义是: 使用auto修饰的变量,是具有自动存储器的局部变量

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

可以用来自动推导类型:

int main()
{
	int a = 0;
    //正常赋值是这样
    int b = a;
}

用auto之后:

根据右边的值推导出左边的值的类型:

int main()
{
	int a = 0;

	auto b = a;//int
	auto c = &a;//int*
	auto& d = a;//int
}

普通场景没有什么价值,类型很长,就有价值,简化代码

std::vector<std::string> v;
std::vector<std::string>::iterator it = v.begin();

可以简化为:

auto it = v.begin();

特别注意: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表达式等进行配合使用。

记住关键字:typeid

C++里面有个typeid关键字,看变量的实际类型,拿到类型字符串(类型名)

#include<iostream>
#include<vector>
#include<string.h>
using namespace std;
int main()
{
	int a = 0;
	//int b = a;
	auto b = a;
	auto c = &a;
	auto& d = a;

	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	cout << typeid(it).name() << endl;

	return 0;
}

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

9.1 范围for的语法

        在C++98中如果要遍历一个数组,可以按照以下方式进行:

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

    // 定义一个数组,接着用for循环访问,之后遍历,正常的流程是这样的:
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
		array[i] *= 2;

	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
		cout << array[i] << " ";
	cout << endl;

    return 0;
}

执行:

        对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因 此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。 

代码测试: 

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

	// 范围for,依次取数组中的数据赋值给e,自定判断结束,自动迭代
   
    for (auto e : array)//范围for一般搭配auto使用,可以灵活地适应数组类型的变化
	{
		cout << e << " ";
	}
	    cout << endl;
}

代码执行:

分析以下代码的问题:预计打印 2 ,4,6,8,10

int main()
{
	int array[] = { 1, 2, 3, 4, 5 };
	for (auto x : array)
	{
		x *= 2;
	}
	for (auto e : array)
	{
		cout << e << " ";
	}
	cout << endl;

	//return 0;
}

实际打印:

        我们知道范围for的作用就是:依次把数组里面的array[0]、array[1]...赋值给x,但是x的改变不会影响数组里面的值,所以要给一个引用:

        那这时候的x赋值,每一次都是array[0]、array[1]...的别名,修改了x就是修改了数组的值:

注意不能这么写!!!

其他知识后面再讲解。

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

我们以之前所学的知识判断以下代码是这样匹配的:

但实际上都会匹配到第一个函数:

源于c++大佬们留下的一个坑:

NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:

#ifndef NULL
#ifdef __cplusplus
#define NULL   0 // 在c++里面是这样定义的
#else
#define NULL   ((void *)0) //不在c++里面,就是这样定义的,比如说c
#endif
#endif

        程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。

        在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。

这个坑还不能随便改,形容的比较贴切:

c++里面为了填补这个坑: 

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

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

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

        c++入门基础的知识讲解到此告一段落,如有错误,欢迎纠正,感谢来访。

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

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

相关文章

企业在什么场景下使用Windows活动目录?

Windows活动目录是微软提供的一种集中式身份验证和访问控制服务&#xff0c;它具有许多功能和优势&#xff0c;因此在很多企业中被广泛使用。那么&#xff0c;企业在什么场景下会选择使用Windows活动目录呢&#xff1f; 首先&#xff0c;Windows活动目录适用于中大型企业或组织…

JavaWeb笔记之前端开发JavaScript

一、引言 1.1 简介 JavaScript一种解释性脚本语言&#xff0c;是一种动态类型、弱类型、基于原型继承的语言&#xff0c;内置支持类型。 它的解释器被称为JavaScript引擎&#xff0c;作为浏览器的一部分&#xff0c;广泛用于客户端的脚本语言&#xff0c;用来给HTML网页增加…

【UML】第9篇 类图

目录 一、类图的概念 二、类图的主要作用 三、类图的构成 3.1 类的名称 3.2 抽象类&#xff08;Abstract Class&#xff09; 一、类图的概念 类图是UML模型中静态视图。它用来描述系统中的有意义的概念&#xff0c;包括具体的概念、抽象的概念、实现方面的概念等。静态视…

spring boot集成mybatis和springsecurity实现权限控制功能

上一篇已经实现了登录认证功能&#xff0c;这一篇继续实现权限控制功能&#xff0c;文中代码只贴出来和上一篇不一样的修改的地方&#xff0c;完整代码可结合上一篇一起整理spring boot集成mybatis和springsecurity实现登录认证功能-CSDN博客 数据库建表 权限控制的意思就是根…

PopChar for Mac 特殊字符输入工具

PopChar Popchar表情输入&#xff0c;一款专门输入特殊字符的软件&#xff0c;让查找和输入特殊字符变得简单快捷方便&#xff0c;可以快速搜索查找表情&#xff0c;还可以将经常发的表情进行收藏&#xff0c;方便下次直接发送&#xff0c;让聊天更加充满快乐&#xff01; 资源…

智能优化算法应用:基于水基湍流算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于水基湍流算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于水基湍流算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.水基湍流算法4.实验参数设定5.算法结果6.…

基本shell功能实现(exec系列程序替换函数练习)

shell 功能描述思路介绍1.实现常驻进程功能2.实现命令读取功能3. 实现命令解析功能4.实现子进程执行命令功能5.完善功能 补充内容让父进程运行内置命令实现子进程能够获得父进程的环境变量功能&#xff08;export命令&#xff09;shell实现重定向功能 全部代码如下&#xff1a;…

CentOS7 安装 DockerCompose

目录 一、安装Docker 二、安装步骤 2.1 卸载 2.2 安装docker 2.3 启动docker 2.4 配置镜像加速器 一、安装Docker Docker 分为 CE 和 EE 两大版本。 CE 即社区版(免费&#xff0c;支持周期7个月)EE 即企业版强调安全&#xff0c;付费使用&#xff0c;支持周期 24 个月…

Linux之进程(五)(进程控制)

目录 一、进程创建 1、fork函数创建进程 2、fork函数的返回值 3、fork常规用法 4、fork调用失败的原因 二、进程终止 1、进程终止的方式 2、进程退出码 3、进程的退出方法 三、进程等待 1、进程等待的必要性 2、wait函数 3、waitpid函数 四、进程程序替换 1、概念…

Java如何将中文转化为拼音

Java中可以使用第三方库pinyin4j来实现中文转拼音。 首先&#xff0c;需要引入pinyin4j的jar包&#xff0c;可以在pinyin4j的官方网站&#xff08;http://pinyin4j.sourceforge.net/&#xff09;下载&#xff0c;也可以通过Maven引入。 Maven引入依赖&#xff1a; <depend…

aws配置以及下载 spaceNet6 数据集

一&#xff1a;注册亚马逊账号 注册的时候&#xff0c;唯一需要注意的是信用卡绑定&#xff0c;这个可以去淘宝买&#xff0c;搜索aws匿名卡。 注册完记得点击登录&#xff0c;记录一下自己的账户ID哦&#xff01; 二&#xff1a;登录自己的aws账号 2.1 首先创建一个用户 首…

数字化指南:数据可视化助力网店腾飞

数据可视化对于网店经营具有重要意义。它不仅仅是一种呈现数据的方式&#xff0c;更是提升网店运营效率和业绩的有力工具。下面我就以可视化从业者的角度&#xff0c;简单聊聊数据可视化能够给网店经营带来的帮助。 在网店经营中&#xff0c;数据可视化能够带来多方面的帮助。…

【PHP入门】2.2 流程控制

-流程控制- 流程控制&#xff1a;代码执行的方向 2.2.1控制分类 顺序结构&#xff1a;代码从上往下&#xff0c;顺序执行。&#xff08;代码执行的最基本结构&#xff09; 分支结构&#xff1a;给定一个条件&#xff0c;同时有多种可执行代码&#xff08;块&#xff09;&am…

基于MLP完成CIFAR-10数据集和UCI wine数据集的分类

基于MLP完成CIFAR-10数据集和UCI wine数据集的分类&#xff0c;使用到了sklearn和tensorflow&#xff0c;并对图片分类进行了数据可视化展示 数据集介绍 UCI wine数据集&#xff1a; http://archive.ics.uci.edu/dataset/109/wine 这些数据是对意大利同一地区种植的葡萄酒进…

Ubuntu 常用命令之 echo 命令用法介绍

echo 是一个在 Ubuntu 系统下常用的命令&#xff0c;主要用于在终端输出字符串或者变量。 echo 的基本语法 echo [option] [string]echo 命令的参数包括 -n&#xff1a;不输出结尾的换行符。-e&#xff1a;启用反斜杠转义字符。-E&#xff1a;禁用反斜杠转义&#xff08;这是…

【论文解读】Efficient SAO Coding Algorithm for x265 Encoder

时间&#xff1a;2015年 级别&#xff1a;IEEE 机构&#xff1a;上海交通大学 摘要 x265是一款开源的HEVC编码器&#xff0c;采用了多种优化技术&#xff0c;具有较快的编码速度和优良的编码性能。作为HEVC的一项关键技术&#xff0c;x265还采用了样本自适应偏移(sample adap…

c++ qt 模态框和阻拦器 优先级 问题 修复 已解决

在c项目中。有 加载动画 和 模态框提醒的功能, 导致发生一个问题&#xff0c;有提示框的时候&#xff0c;动画也停止&#xff0c;必须点击 按钮 所有代码才能有效。 解决办法 谨慎使用 deleteLater,因为和模态框拦截有冲突, 使用 隐藏 或者 删除指针。 deleteLater 使用逻辑是 …

自动气象监测站助力生活生产

随着科技的发展&#xff0c;我们的生活和生产方式正在发生着日新月异的变化。其中&#xff0c;WX-CQ12 自动气象监测站作为一项气象监测设备&#xff0c;正在发挥着越来越重要的作用。它不仅为我们提供了更加准确、实时的天气信息&#xff0c;还为农业、交通、旅游等领域提供了…

全新「机械手」算法:辅助花式抓杯子,GTX 1650实现150fps推断

新方法结合扩散模型和强化学习&#xff0c;将抓取问题分解为「如何抓」以及「何时抓」&#xff0c;平价显卡即可实现实时交互。 手是人类与世界交互的重要部分&#xff0c;手的缺失&#xff08;如上肢残障&#xff09;会大大影响人类的正常生活。 北京大学董豪团队通过将扩散模…

stm32学习总结:4、Proteus8+STM32CubeMX+MDK仿真串口收发

stm32学习总结&#xff1a;4、Proteus8STM32CubeMXMDK仿真串口收发 文章目录 stm32学习总结&#xff1a;4、Proteus8STM32CubeMXMDK仿真串口收发一、前言二、资料收集三、STM32CubeMX配置串口1、配置开启USART12、设置usart中断优先级3、配置外设独立生成.c和.h 四、MDK串口收发…