带领你打开C++的神秘之门--完结篇(下)

简介: 带领你打开C++的神秘之门--完结篇

3.2 使用细节:


使用时必须初始化:


int main()
{
  auto a;
  return 0;
}



原因:


  在编译阶段编译器需要根据初始化表达式来推导auto的实际类型,而未初始化变量,则无法进行推导。注意auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型


  1. 不能直接推导出引用类型.


int main()
{
  int a = 3;
  auto b = a;//如果想b是a的引用,这里并不能达到我们想要的效果.
  //正确写法
  auto& c = a;//此时c就表示是一个引用类型,auto可以推导出是int类型
  return 0;
}


  1. 如果是推导指针类型:


此时,auto和auto*没有区别(记住,只有在推导指针类型时没有区别,试着理解一下,因为指针类型可以直接推导出来)


int main()
{
  int a = 3;
  auto b = &a;//这里auto会自动推导成int*,表示b是一个整形指针int*指向a
  auto* c = &a;//此处auto会推导称为int与*结合为int*
  //所以说此处auto和auto*声明变量没区别.
  return 0;
}


  1. 同一行定义多个变量的情况:


默认将第一个推导出来的类型作为整条语句其他变量的类型.


int main()
{
  auto x = 3, y = 5;        //同类型不会报错
  auto a = 3, b = 4.5, c = 3;   //不同类型会报错,因为会将推导出来的第一个类型作为这条语句所有变量的类型
  return 0;
}


  1. 不能用来声明数组:


int main()
{
  auto x[5] = { 1,2,3,4,5 };//报错
  return 0;
}


  1. 不能做形参推导:


void test(auto a)//报错
{
    ......
}



四、内联函数


概念:


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


//默认Debug下,内联不会起作用.
inline int add(int a, int b)
{
  return a + b;
}
int main()
{
  int c = add(2, 3);
  return 0;
}


需要修改默认属性,更加方便我们在debug版本下观察内联函数.


视频教程:



4.1 观察内联函数的实现:




4.2 内联函数的特点


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


缺陷:由于是在调用处展开,则代码量将会扩大,也就导致目标文件的增大.


优势:少了调用开辟函数栈帧的开销,提高程序运行效率。


  1. 你说内联就内联?编译器是不会信任你的,你将编译器搞崩溃了(代码膨胀咋办?


所以inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同**,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、**不是递归、频繁调用的函数采用inline修饰,否则编译器将不会采用=内联方式.



所以不要以外任何情况下内联都是好的,要视情况而定,对于短小的函数,且大量频繁调用的采用内联比较合适.


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


//test.c(主函数区)
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
#include "add.h"
int main()
{
  int a = 2, b = 3;
  cout << add(a, b) <<endl;
}
//add.c
int add(int a, int b)
{
  return a + b;
}
//add.h
inline int add(int a, int b);


编译器可以编译通过,但是链接不上,因为声明时显示采用内联方式,内联函数的函数名并不会进符号表(因为会展开),而在主函数区中执行add(a, b)时,就找不到函数的地址,因为符号表中找不到.


4.3 相关面试考点:


(1) 宏函数的优缺点?


优点:


1.增强代码的复用性。(在预处理阶段直接进行宏替换)


2.提高性能。(没call指令,不需要为函数开辟栈帧)


缺点:


1.不方便调试宏。(因为预编译阶段进行了替换)


2.导致代码可读性差,可维护性差,容易误用。(标识符不具备指向性)


3.没有类型安全的检查 。(编译器不会报错)


(2) **C++**有哪些技术替代宏?


常量定义 换用const enum


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


五、C++的一颗“语法糖”🍭


5.1 基于范围的for循环


在c++11中,有一种写法是基于范围的for循环,被称为“语法糖”,让我们尝一尝这颗“糖”甜不甜吧?🍭🍭🍭


以前我们打印数组中的每个元素,我们是这样的.


int main()
{
  int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
  //对数组的每个元素*2
  for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
  {
    arr[i] *= 2;
  }
  //打印每个元素
  for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
  {
    cout << arr[i] << " ";
  }
  return 0;
}


使用“语法糖”后:


int main()
{
  int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
  //对数组的每个元素*2
  错误写法
  //for (auto x:arr)
  //{
  //  x *= 2;
  //}
  //正确写法
  for (auto& x:arr)
  {
    x *= 2;
  }
  //打印每个元素
  for (auto x : arr)
  {
    cout << x << " ";
  }
  return 0;
}


解释:


 对于一个有范围的集合而言,程序员来手动再写一遍范围是没有必要的,多余之举,有时候还会写错(例如:忘记下标从0开始)。此事交给任劳任怨的编译器完成比较好,因此C++11标准中引入了基于范围的for循环。


格式:


for循环后的括号由冒号(😃 分为两部分:


第一部分:范围内用于迭代的变量(取名随意,与给变量名起名一样,有意义即可),


第二部分:表示被迭代的范围,数组名表示范围是整个数组.


5.2 “糖虽好,注意粘牙哦!”


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


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


注意:以下代码就有问题,因为for的范围不确定


void print_for(int array[])//数组传参过来之后就是首元素的地址,而不是整个数组,所以范围不确定
{
  for (auto& e : array)
    cout << e << endl;
}
int main()
{
  int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
  print_for(arr);
  return 0;
}


六、认识nullptr


  在C语言阶段,我们提倡创建一个变量之后,要给定一个初始值,减少一些可能出现的未知错误(例如:野指针,随机值等),对于指针我们经常使用下面这段代码进行初始化.也就是NULL.


#include <stdio.h>
int main()
{
  int* p = NULL;
  return 0;
}


那NULL究竟是什么呢?我们右击NULL,在弹出的快捷菜单中,选择“转到定义”命令,可以查看到下面这段解释.



 很明显,NULL就是个宏定义,而这里NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。


不论采取上面何种定义,在使用空值(NULL)的指针时,都不可避免的会遇到一些麻烦 .


栗子:


void f(int)
{
  cout << "f(int)" << endl;
}
void f(int*)
{
  cout << "f(int*)" << endl;
}
int main()
{
  f(0);
  f(NULL);//这里想调用f(int*)函数
  f((int*)NULL);
  return 0;
}


运行结果:


f(int)


f(int)


f(int*)


 这里因为NULL被定义为0,所以并没有调用f(int*)函数成功.因为语言必须向下兼容,所以不能直接修改NULL的定义,为了解决这一问题,C++11中引入了新的的nullptr为void*类型.


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


注意:


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


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


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

目录
相关文章
|
1月前
|
Java 编译器 C++
【C++】 | 类和对象完结篇
【C++】 | 类和对象完结篇
|
10月前
|
存储 安全 编译器
带领你打开C++的神秘之门--完结篇(中)
带领你打开C++的神秘之门--完结篇
71 0
|
10月前
|
编译器 Linux C语言
带领你打开C++的神秘之门--完结篇(上)
带领你打开C++的神秘之门--完结篇
95 0
|
10月前
|
编译器 程序员 C语言
带领你打开C++神秘之门--入门篇
带领你打开C++神秘之门--入门篇
38 0
|
11月前
|
Java 编译器 C++
【C++】类和对象(完结篇)(二)
【C++】类和对象(完结篇)
71 0
|
11月前
|
存储 编译器 C++
【C++】类和对象(完结篇)
【C++】类和对象(完结篇)
50 0
|
编译器 C++
<C++>运算符重载完结,详解赋值,关系,函数调用运算符
<C++>运算符重载完结,详解赋值,关系,函数调用运算符
109 0
<C++>运算符重载完结,详解赋值,关系,函数调用运算符
|
5天前
|
存储 编译器 C语言
c++的学习之路:5、类和对象(1)
c++的学习之路:5、类和对象(1)
21 0
|
5天前
|
C++
c++的学习之路:7、类和对象(3)
c++的学习之路:7、类和对象(3)
19 0