当前位置: 首页 > news >正文

C++入门·收尾

在这里插入图片描述

你好,我是安然无虞。

文章目录

  • 学习网站
  • 写在前面
  • 内联函数
    • 概念
    • 特性
    • 面试题
  • auto关键字(C++11)
    • auto简介
    • auto的使用细则
    • auto不能推导的场景
  • 基于范围的for循环(C++11)
    • 范围for的语法
    • 范围for的使用条件
  • 指针空值nullptr(C++11)
    • C++98中的指针空值
  • 大厂面试真题

学习网站

推荐给老铁们两款学习网站:
面试利器&算法学习:牛客网
风趣幽默的学人工智能:人工智能学习
首个付费精品专栏:《C++入门核心技术》

写在前面

前面讲到的函数重载和引用是C++入门部分的重点,校招时也很重要,所以大家要牢牢掌握哦,今天的内容就简单多了,加油吧少年们。

内联函数

概念

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

int Add(int left, int right)
{
	return left + right;
}

int main()
{
	int ret = 0;
	ret = Add(1, 2);

	return 0;
}

在这里插入图片描述

上面的Add函数是普通函数,调用时会建立栈帧,压栈等一系列操作,如果频繁调用Add函数,效率上会有一定的损失。
那C语言是如何解决频繁调用像Add这种短小函数导致效率损失的问题呢?在讲解函数重载那节时,编译的预处理过程会发生头文件展开、宏替换、删除注释、条件编译这四个过程,所以这里的答案是定义宏函数,比如:

#define ADD(x, y) ((x) + (y))

C语言是定义宏函数,那C++是如何做的呢?C++是在Add函数前增加 inline 关键字,将其改为内联函数,这样的话在编译期间编译器会用函数体替换函数的调用(在调用内联函数处函数体直接展开)。
在这里插入图片描述
可能老铁会有一个疑惑,为什么C语言都有了宏函数这样的方法,C++还要大费周折去搞一个inline呢?那是因为啊,宏函数极容易写错,比如少加一个括号等,所以C++引入inline解决宏函数晦涩难懂、容易写错的问题,而且宏函数还不支持调试。

特性

  1. inline是一种以空间换时间的做法,省去了调用函数建立栈帧的开销,所以代码很长(一般函数体在10行以内可以,具体取决于编译器)或者有循环/递归的函数都不适宜作为内联函数;
  2. inline 对于编译期而言只是一个建议,编译器会自动优化,如果定义成 inline 的函数体内有循环/递归等,编译器优化时会忽略掉内联;
  3. 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(1);
	return 0;
}

面试题

1、宏的优缺点?
优点:

  • 增强代码的复用性;
  • 提高性能。

缺点:

  • 不方便调试宏;(预编译阶段进行了宏替换)
  • 导致代码可读性差,可维护性差,容易误用;
  • 没有类型安全的检查。

2、C++有哪些技术替代宏?

  1. 常量定义换用const;
  2. 函数定义换用内联函数。

auto关键字(C++11)

编译器支持C++11时才可以使用auto关键字。

auto简介

C++11规定 auto 作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得

int test()
{
	return 10;
}

int main()
{
	int a = 10;
	auto b = a;
	auto c = 'a';
	auto d = test();

	cout << typeid(a).name() << endl;//可认为typeid(x).name()返回的是变量x的类型
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;

	//auto e;无法通过编译,使用auto定义变量时必须对其进行初始化
	return 0;
}

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

auto的使用细则

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

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;

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

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

可能有老铁会疑惑,这样看来auto好像也没有太大的用处,其实不然,不信你看:

//以后我们学习STL时可能会写这样的代码:
std::map<std::string, std::string> dict;
dict["sort"] = "排序";
dict["string"] = "字符串";
std::map<std::string, std::string>::iterator it = dict.begin();
//用auto直接就简短了很多
auto it = dict.begin();

可能老铁现在还看不懂上面的代码,没关系,你现在可以知道我要表达的意思就行了,后面这些都会详细说明。
所以auto一个很重要的意义之一就是:
类型很长时,可以不写,用auto推导

auto不能推导的场景

1、auto不能作为函数的参数

//此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导(给缺省参数也不行)
//因为不知道实参类型具体是什么,建立栈帧时不知道要建立多大
void Test1(auto a)
{}

2、auto不能直接用来声明数组

void Test2()
{
	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)

范围for的语法

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

int array[] = { 1, 2, 3, 4, 5, 6 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); i++)
	array[i] *= 2;

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

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

int array[] = { 1,2,3,4,5 };
//依次自动取array中的数据,赋值给e,自动判断结束
for (auto& e : array)//想想为什么用的是引用?
	e *= 2;

for (auto e : array)
	cout << e << " ";

如果想恢复成原来数组的数据,这样可以吗?

for(auto e : array)
	e \= 2;

这样是错误的,因为e改变不会改变array数组中的值,e只是array数组值的一份拷贝,那怎么办呢?用引用即可(不是拷贝,而是别名)

注意:与普通循环相似,可以用continue来结束本次循环,也可以用break来跳出整个循环。

范围for的使用条件

1、for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是循环迭代的范围;
注意:以下代码就有问题,因为for的范围不确定。

//这里的array是指针,并不确定其范围,形参传过来的是数组首元素的地址
void TestFor(int array[])
{
	for(auto& e : array)
		cout << e << endl;
}

2、迭代的对象要实现++和==的操作。(关于迭代器,后面会详细讲解)

关于范围for的底层以后会讲解,大家现在了解会用即可。

指针空值nullptr(C++11)

C++98中的指针空值

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

void TestPtr()
{
	int* p1 = NULL;
	int* p2 = 0;
}

NULL实际上是一个宏,在传统的C语言头文件(stddef.h)中,可以看到如下代码:
在这里插入图片描述
可以看到C++将NULL定义成了字面常量0,在实际编程中NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采用何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如:

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;
}

程序本意是想通过 f(NULL) 调用指针版本 f(int*) 函数,但是由于NULL被定义成0,因此和程序的初衷相悖。
在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void*)0。
注意:

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

大厂面试真题

1、关于c++的inline关键字,以下说法正确的是( )
A.使用inline关键字的函数会被编译器在调用处展开
B.头文件中可以包含inline函数的声明
C.可以在同一个项目的不同源文件内定义函数名相同但实现不同的inline函数
D.递归函数也都可以成为inline函数

解析:文章理解之后这道题很简单,选C

2、在( )情况下适宜采用 inline 定义内联函数
A.函数体含有循环语句
B.函数体含有递归语句
C.函数代码少、频繁调用
D.函数代码多,不常调用

解析:跟上一题一样简单

相关文章:

  • 25.CF992E Nastya and King-Shamans 转化+线段树二分
  • 快来带您了解中秋节的前世今生
  • 分布式锁之防止超卖 --mysql原子操作,乐观锁,redis事务,乐观锁
  • 【算法刷题】第一篇——哈希
  • 小脚本杂文shell脚本
  • 网络热的查询易语言代码
  • 医美健康这类在医疗行业的推广要怎么做?
  • uni-app开发,防止踩坑
  • mac M1 安装AndroidStudio打开真机调试
  • 备战数学建模40-遗传算法优化bp神经网络(攻坚站4)
  • 解决Java使用response下载文件报错,并总结可能出错的原因: java.io.IOException: 你的主机中的软件中止了一个已建立的连接。
  • C# .Net AOP 演进历史POP OOP 代码细节篇
  • Pytorch学习——入门知识
  • LeetCode 刷题系列 -- 1254. 统计封闭岛屿的数目
  • sizeof()与strlen()在指针和数组笔试题(超详细!!!绝对有帮助!!!)
  • [译]前端离线指南(上)
  • 【Linux系统编程】快速查找errno错误码信息
  • 002-读书笔记-JavaScript高级程序设计 在HTML中使用JavaScript
  • 03Go 类型总结
  • Android 初级面试者拾遗(前台界面篇)之 Activity 和 Fragment
  • android百种动画侧滑库、步骤视图、TextView效果、社交、搜房、K线图等源码
  • android图片蒙层
  • CentOS 7 修改主机名
  • CSS居中完全指南——构建CSS居中决策树
  • Fabric架构演变之路
  • JavaScript的使用你知道几种?(上)
  • js ES6 求数组的交集,并集,还有差集
  • leetcode讲解--894. All Possible Full Binary Trees
  • MySQL几个简单SQL的优化
  • 第十八天-企业应用架构模式-基本模式
  • 七牛云 DV OV EV SSL 证书上线,限时折扣低至 6.75 折!
  • 想使用 MongoDB ,你应该了解这8个方面!
  • 小程序滚动组件,左边导航栏与右边内容联动效果实现
  • 移动端唤起键盘时取消position:fixed定位
  • 由插件封装引出的一丢丢思考
  • 自定义函数
  • Oracle Portal 11g Diagnostics using Remote Diagnostic Agent (RDA) [ID 1059805.
  • JavaScript 新语法详解:Class 的私有属性与私有方法 ...
  • 整理一些计算机基础知识!
  • ​MPV,汽车产品里一个特殊品类的进化过程
  • # 计算机视觉入门
  • #考研#计算机文化知识1(局域网及网络互联)
  • #我与Java虚拟机的故事#连载09:面试大厂逃不过的JVM
  • (java版)排序算法----【冒泡,选择,插入,希尔,快速排序,归并排序,基数排序】超详细~~
  • (Matalb时序预测)WOA-BP鲸鱼算法优化BP神经网络的多维时序回归预测
  • (SpringBoot)第二章:Spring创建和使用
  • (zz)子曾经曰过:先有司,赦小过,举贤才
  • (官网安装) 基于CentOS 7安装MangoDB和MangoDB Shell
  • (论文阅读23/100)Hierarchical Convolutional Features for Visual Tracking
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理 第13章 项目资源管理(七)
  • (三)Honghu Cloud云架构一定时调度平台
  • (三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练
  • (四)docker:为mysql和java jar运行环境创建同一网络,容器互联
  • (一) springboot详细介绍
  • (一)Neo4j下载安装以及初次使用