【C++程序员的自我修炼】基础语法篇(二)

风力掀天浪打头

只须一笑不须愁


目录

内联函数 

概念💞

 性质 ⭐

不建议变量分离

inline的优劣势

 inline的局限性

auto关键字

auto的概念💞

auto的使用细则💞

auto不能推导的场景 💞

auto基于范围的for循环💞

指针空值nullptr

 

内联函数 

契子

在大型项目中我们会对某一种函数进行反复调用,比如排序中的 Swap ,调用函数所带来的后果是建立栈帧,多次调用就要建立多重栈帧严重影响运行效率。

在 C 语言阶段我们曾采用宏定义的方式来解决这个问题,比如说两数相加函数

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

优势在调用的地方直接展开(替换),增强代码的复用性,没有函数调用建立栈帧的开销

劣势无法对宏定义中的变量进行类型检查,嵌套定义过多影响程序的可读性,容易出错

为了解决这个问题,我们的 C++ 推出了内联函数:

概念💞

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

以下将会用汇编代码解释这个问题:

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

查看方式🌤️

在release模式下,查看编译器生成的汇编代码中是否存在call Add
在debug模式下,需要对编译器进行设置,否则不会展开 

 在debug模式下对编译器的设置:

这个时候我们发现,Add函数展开了 

 性质 ⭐

不建议变量分离

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

例如,我们先来看以下代码:

无法解析的外部符号 "int __cdecl Add(int,int)" (?Add@@YAHHH@Z)函数main中引用了该符号   所以 inline 不建议声明和定义分离为好

inline的优劣势

inline 是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用

🌤️缺陷:可能会使目标文件变大
🌤️优势:少了调用开销,提高程序运行效率

 inline的局限性

inline 对于编译器而言只是一个建议,不同编译器关于 inline 实现机制可能不同,一般建议:将函数规模较小、不是递归、且频繁调用的函数采用 inline 修饰,否则编译器会忽略 inline 特性(函数调用不展开

auto关键字

auto的概念💞

随着程序越来越复杂,程序中用到的类型也越来越复杂

经常体现在:

类型难于拼写

含义不明确导致容易出错

例如~

#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 是一个类型,但是该类型太长了,特别容易写错

在 C语言阶段 我们可以通过 typedef 给类型取别名,比如:

typedef std::map<std::string, std::string> Map
typedef int SListDataType;

 为我们 C++ 中提供了一个很方便的关键字 auto自动类型推导

std::map<std::string, std::string> m;
<1>std::map<std::string, std::string>::iterator it = m.begin();
<2>auto it = m.begin();

其中 <1>、<2>的写法是等价的,auto的作用就是从右边推导类型、简化代码

例如

#include<iostream>
int main()
{
	auto a = 0;
	return 0;
}

因为 表达式a 的右边为整型,所以 auto 推导的类型就为 int 

我们可以用 typeid 进行验证:

#include<iostream>
#include<typeinfo>
using std::cout;
using std::endl;
int TestAuto(int n = 0)
{
	return n;
}
int main()
{
	auto a = 0;
	auto b = 0.0;
	auto c = '0';
	auto d = TestAuto();
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	return 0;
}

<1>使用 auto 定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto 的实际类型

<2> auto 并非是一种 (类型) 的声明,而是一个类型声明时的 (占位符) ,编译器在编译期会将 auto 替换为变量实际的类型

auto的使用细则💞

注意💞

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

auto 与指针和引用结合起来使用 auto 声明指针类型时,用 autoauto* 没有任何区别,但用auto* 声明引用类型时则必须加 &

#include<iostream>
#include<typeinfo>
using std::cout;
using std::endl;
int main()
{
	int x = 10;
	auto a = &x;    
	auto* b = &x;  
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
}


在同一行定义多个变量

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

auto不能推导的场景 💞

auto不能作为函数的参数

错误示范

#include<iostream>
#include<typeinfo>
using std::cout;
using std::endl;

void Fun(auto a)
{
	cout << a << endl;
}

int main()
{
	Fun(5);
	return 0;
}

 

 auto不能直接用来声明数组

错误示范 

#include<iostream>
#include<typeinfo>
using std::cout;
using std::endl;

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

那硬是要用 auto 声明数组该怎么办呢? 

#include<iostream>
#include<typeinfo>
using std::cout;
using std::endl;
int main()
{
	int a[] = { 1,2,3 };
	auto b = a;
	cout << typeid(b).name() << endl;
    return 0;
}

我们可以看到 b 是一个 int* 的类型 

如果表达式为数组且 auto 带上&,则推导类型为数组类型

auto基于范围的for循环💞

我们平时写 C语言 的循环是不是特别麻烦,例如以下的代码

	int a[] = { 1,3,5,7,9,2,4,6,8,0 };
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		printf("%d ", a[i]);
	}

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误

因此 C++ 中引入了基于范围的for循环

for 循环后的括号由冒号“ ”分为两部分:

第一部分是范围内用于迭代的变量(自己喜欢用什么就写什么
第二部分则表示被迭代的范围(数组名
	int a[] = { 1,3,5,7,9,2,4,6,8,0 };
	for (int i : a)
		cout << i << " ";

这个方法的爽点就在于:自动判断循环条件,执行完语句变量自动 ++ 或 -- 

到这里就有人问这个循环只能遍历吗?能不能修改数组的值呢?

答案是可以的,以下我们来看:

#include<iostream>
#include<typeinfo>
using std::cout;
using std::endl;
int main()
{
	int a[] = { 1,2,3,4,5 };
	for (int& i : a)
	{
		i *= 2;
		cout << i << " ";
	}
    return 0;
}

这里传引用的目的就是为了改变该地址的内容 

注意

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

优点:简化代码、减少出错率

局限性:范围的 for 循环的底层是 begin、end 两个迭代器相当于从数组的首元素开始到尾元素,一遍历就是整个数组

auto 的出现解决了范围 for 循环变量类型有时不好写的问题以及可以无脑循环整个数组🌤️

	int a[] = { 1,2,3,4,5 };
	for (auto i : a)
		cout << i << " ";

 for (auto i : a) 就成了循环遍历整个数组的模板,因为自动类型推导嘛,不用考虑类型

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

错误示范

#include<iostream>
#include<typeinfo>
using std::cout;
using std::endl;

void Fun(int* a)
{
	for(auto i: a)
		cout << i << " ";
}

int main()
{
	int a[] = { 1,2,3,4,5 };
	return 0;
}

因为数组传参传的是数组首元素地址从而导致 begin 和 end  不完整,所以报错

指针空值nullptr

  • 是否可以混着用 NULL和 nullptr呢?
  • 这两者到底有什么区别呢?
  • 滥用NULL到底会带来什么不可思议的错误?

别急接下来我们谈谈他们两者中的那些破事

NULL的介绍

🔥在良好的 C/C++ 编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化:

 int* p1 = NULL;
 int* p2 = 0;

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

 #ifndef NULL
 #ifdef __cplusplus
 #define NULL    0
 #else
 #define NULL    ((void *)0)
 #endif
 #endif

NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦

#include<iostream>
using namespace std;
void f(int)
{
	cout << "f(int)" << endl;
}
void f(int*)
{
	cout << "f(int*)" << endl;
}
int main()
{
	f(0);
	f(NULL);
	f((int*)NULL);
	return 0;
}

可以发现 NULL 在传参的时候竟然走了 int 类型!!!

我们想 f(NULL) 调用指针版本的 f(int*) 函数,但是由于 NULL 被定义成 0,走了 f(int) 函数

NULL 是具有一定风险的:

NULL终究只是一个宏。它是一个整型,它不是指针。因而随之带来的问题是,我们没有办法在不显示声明指针类型的情况下定义一个空指针。

nullptr的介绍

🔥而在 C++ 中有一个关键字完美的解决了这个问题——nullptr。作为一个字面常量和一个零指针常数,它可以被隐式转换为任何指针类型

拿上面那个栗子看

    f(0);
	f(nullptr);
	f((int*)NULL);

 nullptr 只能隐式转换为 int 类型吗?我们来看

#include <iostream>
using namespace std;

void Fun(void* c) {
    cout << "Fun(void* c)" << endl;
}
void Fun(int n) {
    cout << "Fun(int n)" << endl;
}
int main() {
    Fun(NULL);
    Fun(nullptr);
    return 0;
}

由于 nullptr 无法隐式转换为整形,而可以隐式匹配指针类型

特性:

nullptr 是一个有用的小特性,它能让你的代码更安全

sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同

#include <iostream>
using namespace std;

int main() 
{
    cout << sizeof(nullptr) << endl;
    cout << sizeof((void*)0) << endl;
    return 0;
}

 

先介绍到这里啦~

有不对的地方请指出💞

 

  • 15
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

烟雨长虹,孤鹜齐飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值