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

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

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

https://developer.aliyun.com/article/1456990?spm=a2c6h.13148508.setting.24.2e124f0eJ8obyo


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


bfb7d5ea82b14df4b89f6becb5da562a.png


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


执行:


295ab49974bc4041a011992514885943.png

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


a57872345ef74ed9835cf76b210f0c7e.png


代码测试:


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

代码执行:



e4fe443a17ed49c8a54243bc9703ca2c.png

分析以下代码的问题:预计打印 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;
}

实际打印:


7c7431423adf4f5da4ce765c4bb4343f.png


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



7dce905f7b224b13aab16a277f55643e.png

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


e2de63255d8548d887f3233a2e4a65af.png


注意不能这么写!!!:


c39f96ceaac24b6b99e4ecc8cb36f99e.png


其他知识后面再讲解。


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

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



1ea9ff5f8a6740a7a3f13d05f5818bec.png

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


468d6107102b49c1abf4f668543648e0.png


源于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。


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


d7e32129ac8943fea0cd3dec46a2440f.png


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


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

 

相关文章
|
17天前
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
30 2
C++入门12——详解多态1
|
17天前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
45 1
|
6天前
|
存储 安全 编译器
【C++打怪之路Lv1】-- 入门二级
【C++打怪之路Lv1】-- 入门二级
13 0
|
6天前
|
自然语言处理 编译器 C语言
【C++打怪之路Lv1】-- C++开篇(入门)
【C++打怪之路Lv1】-- C++开篇(入门)
12 0
|
15天前
|
分布式计算 Java 编译器
【C++入门(下)】—— 我与C++的不解之缘(二)
【C++入门(下)】—— 我与C++的不解之缘(二)
|
15天前
|
编译器 Linux C语言
【C++入门(上)】—— 我与C++的不解之缘(一)
【C++入门(上)】—— 我与C++的不解之缘(一)
|
17天前
|
编译器 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
26 0
|
17天前
|
程序员 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
30 0
|
17天前
|
存储 算法 C++
C++入门10——stack与queue的使用
C++入门10——stack与queue的使用
35 0
|
17天前
|
存储 C++ 容器
C++入门9——list的使用
C++入门9——list的使用
17 0