【深蓝学院C++】1-6章知识点总结笔记

第1章:C++初探

1. 从 Hello World 谈起

(1)函数:反复调用的代码

  • 返回类型
  • 函数名
  • 形参列表
  • 函数体

(2)main函数:特殊的函数,作为整个、程序的入口

  • 返回类型 int,表示程序的返回值,通常使用0表示正常返回
  • 形参列表可以为空

(3)类型

(4)语句:表明需要执行的操作

(5)注释

​ 单行注释

​ 多行注释

2. 系统IO

(1)iostream:标准库所提供的IO接口,用于用户交互

​ 使用 <>:表示从系统环境变量所指定的位置中寻找

​ 使用" ":表示从当前项目下寻找

​ 输入流:cin;输出流:cout/cerr/clog

​ cerr与clog的区别:是否立即刷新缓冲区,cerr(错误信息)立即刷新缓冲区,clog(日志信息)不立即刷新缓冲区。

​ 缓冲区刷新:std::flush,刷新加换行使用std::endl

(2)名字空间:用于防止名称冲突

​ std 名字空间:C++标准库提供包含在std中

​ 访问名字空间中元素的三种方式:域解析符:: ,using语句(有名字冲突的可能),名字空间别名

​ 名字空间与名称改编(name mangling):文件链接时不希望存在::,可能会对链接产生影响,所以对名称进行变换

​ 如果使用C的系统IO,则需要包含 cstdio

​ C/C++系统IO比较:

​ - printf:使用直观但容易出错

​ - cout:不易出错但书写较长

​ - C++20格式化库:新的解决方案,解析为字符串

3.控制流

(1)if语句

(2)==与=

​ = :赋值操作

​ == :判断两个值是否相等

​ 将常量放在==左边防止误用

(3)while语句

4.结构体与自定义数据类型

(1)结构体:将相关数据放置在一起

​ -通过点操作符访问内部数据

​ -可以作为函数的输入参数或返回类型

​ -可以引入成员函数,更好地表示函数与数据的相关性

第2章 对象与基本类型

1.从初始化/赋值语句谈起

初始化/赋值:功能是将某个值与一个对象关联起来

​ -值:字面值1,2;对象(变量或常量)所表示的值x=y

​ -标识符:变量,常量,引用

​ -初始化基本操作:

​ 在内存中开辟空间,保存相应的数值、

​ 在编译器中构造符号表,把标识符与相关内存空间关联起来

​ -值与对象均有类型

​ -初始化/赋值可能涉及类型转换(类型不匹配)

2.类型

1.基本概念

(1)类型是一个编译器概念,可执行文件不存在类型的概念

(2)C++是强类型语言

(3)引入类型是为了更好地描述程序,防止误用

(4)类型具体描述了:

​ 1)存储所需要的尺寸(sizeof)

​ 2)取值空间(std::numeric_limits,超过范围可能产生溢出)

​ 3)对齐信息(alignof)对结构体的大小产生影响

​ 4)可执行的操作

(5)类型分类:基本类型与复杂类型

​ 1)基本(内建)类型:

​ 数值类型:字符(char, wchar_t, char16_t, char32_t),整数,浮点数

​ void

​ 2)复杂类型

​ 基本类型组合、变种所产生的类型,可能是标准库引入,或自定义类型

(6)与类型相关的标准未定义部分

​ 1)char 是否有符号

​ char:不确定

​ unsinged char:无符号

​ singed char:有符号

​ 2)整数中内存中的保存方式:大端,小端

​ 3)类型的大小会间接影响取值范围

​ C++11 中引入了固定尺寸的整数类型,如 int32_t,保证可移植性

2.字面值及其类型

(1)字面值:程序中直接表示为一个具体数值或字符串的值

(2)每个字面值都有类型

​ 1)整数字面值:20(十进制) 024(八进制)0x14(十六进制)

​ 2)浮点数:1.3

​ 3)字符字面值:‘c’,‘\n’

​ 4)字符串字面值:“Hello” char[6]型

​ 5)布尔字面值:true , false

​ 6)指针字面值:nullptr -nullptr_t型

3.变量及其类型

(1)变量对应一段存储空间,可以改变其中的内容

(2)变量的类型在其首次声明(定义时)指定

​ 变量声明与定义的区别:文件外已定义的的变量在文件内使用需要声明,在变量名前加extern前缀

(3)变量的初始化与赋值

​ 1)初始化

​ 缺省初始化

​ 直接int x(10)/拷贝初始化int x = 10

​ 其他初始化

4.(隐式)类型转换

(1)变量赋值时涉及到的类型转换

​ 1)bool与整数之间的转换

​ 2)浮点数与整数

(2)隐式类型转换不只发生在赋值时

​ 1)if判断

​ 2)赋值比较:unsinged与int类型比较时,int会转换成unsigned int

3.复合类型:从指针到引用

(1)指针:一种间接类型

​ 1)特点

​ 可以指向不同对象

​ 具有相同的尺寸

​ 2)操作

​ &:取地址

​ *:解引用

(2)指针的定义

(3)nullptr

​ 1)一个特殊的对象(类型为nullptr_t),表示为空指针

​ 2)类似与C中NULL,但更加安全

(4)指针与bool类型的隐式转换

​ true:指向非空

​ false:指向为空

(5)主要操作:解引用,增加,减少,判等

(6)void* 指针

​ 没有记录对象的尺寸信息,可以保存任意地址

​ 支持判等操作

(7)指针的指针

​ 不太常用

(8)引用

int x = 3;
int& ref = x;

ref 为 x 的别名(ref 是对象的别名,不能绑定到字面值 )

4.常量

(1)声明

​ const

(2)编译器保证不可写

​ 保证:防止非法操作,优化程序逻辑

(3)常量指针与指针常量

​ 常量指针:指向常量的指针 const int* ptr = &x;

​ 指针常量:int* const ptr = &x;

(4)常量引用

​ const int&

​ 主要用于函数形参(通常用于较大对象,如结构体或类)

​ 可以绑定字面值:const int & ref = 3;

(5)常量表达式(C++11新标准)

​ 使用 constexpr 声明

​ 声明的是编译期常量

int x;
std::cin >> x;

const int y1 = x;        //y1的值运行期确定
//const int y2 = 3;        //y2的值编译期确定
constexpr int y2 = 3;    //更易进行后续优化

if(y1 == 3)
{

}
if(y2 == 3)
{

}

​ 常量表达式指针:constexpr const int* ptr = nullptr;

​ 其中,ptr 的类型为: const int* const

5.类型别名

//1
typedef int myInt;
typedef char myCharArr[4];
//2从c++11开始
using myInt = int;
using myCharArr = char[4]; 

类型别名与指针引用的关系:

​ 应将指针类型别名视为一个整体,在此基础上引入常量表示指针为常量的类型

using IntPtr = int*;
int main(){
    int x = 3;
    const int* ptr = &x;    //const修饰int
    const IntPtr ptr = &x;    //等价于int* const ptr
}

​ 不能通过类型的别名构造引用的引用

6.类型自动推导

常见形式:

​ ●auto: 最常用的形式,但会产生类型退化
​ ● const auto / constexpr auto: 推导出的是常量 / 常量表达式类型
​ ● auto& : 推导出引用类型,避免类型退化
​ ● decltype(exp) :返回 exp 表达式的类型(表达式若为左值则加引用)
​ ● decltype(val) :返回 val 的类型
​ ● decltype(auto) :从 c++14 开始支持,简化 decltype 使用
​ ● concept auto :从 C++20 开始支持,表示一系列类型( std::integral auto x = 3; )

#include 
int main()
{
    int x = 3;
    int* ptr = &x;

    const int y1 = 3;
    const int& y2 = y1;

    std::cout << std::is_same_v<decltype(3.5 + 15l), double> << std::endl;
    std::cout << std::is_same_v<decltype(*ptr), int&> << std::endl;
    std::cout << std::is_same_v<decltype(ptr), int*> << std::endl;
    std::cout << std::is_same_v<decltype(x), int> << std::endl;
    std::cout << std::is_same_v<decltype((x)), int&> << std::endl;
    std::cout << std::is_same_v<decltype(y1), const int> << std::endl;
    std::cout << std::is_same_v<decltype(y2), const int&> << std::endl;
    std::cout << std::is_same_v<decltype((y1)), const int&> << std::endl;
    std::cout << std::is_same_v<decltype((y2)), const int&> << std::endl;

    decltype(auto) x = 3.5 + 15l;
}

7.域与对象的生命周期

域(scope)

全局域(global scope):程序最外围的域,其中定义的为全局变量。

块域(block scope):使用大括号所限定的域,其中定义的是局部对象。

第3章 数组、vector与字符串

1.数组

(1)数组的复杂声明

​ 1)指针数组

int x1;
int x2;
int x3;
int *a[3] = {&x1, &x2, &x3};

​ 2)数组的指针

int (*a)[3];    //a得到类型为int(*)[3]

​ 3)数组的引用

int b[3];
int (&a)[3] = b;    //a的类型int(&)[3]

(2)数组元素的访问

​ 使用时通常会转换成相应的指针类型

​ x[y] --> *((x) + (y))

(3)数组到指针的隐式转换

​ 使用数组对象时,通常会产生数组到指针的隐式转换,隐式转换会丢失一部分类型信息

​ 可通过声明引用避免隐式转换

int a[3] = {1,2,3};        //a的类型为int[3]

auto b = a;        //b的类型为int*
auto& b = a;    //b的类型为int(&)[3]

source.cpp

int array[3] = {1,2,3};
void fun()
{
    std::cout << array << std::endl;    //输出数组元素首地址
}

main.cpp

void fun();        //声明
extern int array[];        //unknown Bounded Array
//int array[]    不完整声明
int main()
{
    fun();
    std::cout << array << std::endl;
    std::cout << array[0] << std::endl;
}

​ 获得指向数组开头与结尾的指针 : std::©begin, std::©end

int a[3] = {1,2,3};
std::cout << a << ' ' << &(a[0]) << ' ' << std::begin(a) << std::endl;

std::begin(a);        //int*
std::cbegin(a);        //const int*

(4)其他操作

​ 1)求元素个数

int a[3];
std::cout << sizeof(a) / sizeof(int) << std::endl;
int a[3];
std::cout << std::size(a) << std::endl;        //推荐
int a[3];
std::cout << std::end(a) std::begin(a) << std::endl;

​ 2)元素遍历

//基于元素个数
int a[4] = {2, 3, 5, 7};
size_t index = 0;

while(index < std::size(a))
{
    std::cout << a[index] << std::endl;
    index = index +1;
}
//基于 (c)begin/(c)end
int a[4] = {2, 3, 5, 7};

auto ptr = std::cbegin(a);
while(ptr != std::cend(a))
{
    std::cout << *ptr << std::endl;
    ptr = ptr + 1;
}
//基于 range-based for 循环
for (int x : a)
{
    std::cout << x << std::endl;
}

​ 3)c字符串

#include 
-------------------------------------
char str[] = "Hello";
//char str[] = {'H', 'e', 'l', 'l', 'o', '\0'};
auto ptr = str;
std::cout << strlen(str) << std::endl;
std::cout << strlen(ptr) << std::endl;

(5)多维数组

​ 1)本质:数组的数组

​ 2)索引与遍历

int x[3][4] = {1, 2, 3, 4, 5};
for (auto& p: x)
{
    for (auto& q: p)
    {
        std::cout << q << '\n';
    }    
}
int x[3][4] = {1, 2, 3, 4, 5};
size_t index0 = 0;
while(index0 < 3)
{
    size_t index1 = 0;
    while(index1 < 4)
    {
        std::cout << x[index0][index1] << std::endl;
        index1 = index1 + 1;
    }
    index0 = index0 + 1;
}
int x[3][4] = {1, 2, 3, 4, 5};
size_t index0 = 0;
while(index0 < std::size(x))
{
    size_t index1 = 0;
    while(index1 < std::size(x[index0]))
    {
        std::cout << x[index0][index1] << std::endl;
        index1 = index1 + 1;
    }
    index0 = index0 + 1;
}

​ 3)指针与多维数组

​ 多维数组可以隐式转换为指针,但只有最高维会进行转换,其它维度的信息会被保留。

int x[3][4];
auto ptr = x;    //等价于 int (*ptr)[4] = x

​ 使用类型别名来简化多维数组指针的声明。

using A2 = int[4][5];
using A = int[4];
int mian()
{
    int x[3][4][5];
    //auto ptr = x;
    A2* ptr = x;

    //int x2[3][4];
    A x2[3];    
    A* ptr2 = x2;
}

​ 使用指针遍历多维数组。

int main()
{
    int x[3][4];
    auto ptr = std::begin(x);
    while (ptr != std::end(x))
    {
        auto ptr2 = std::begin(*ptr);
        while (ptr2 != std::end(*ptr))
        {
            std::cout << *ptr2 << std::endl;
            ptr2 = ptr2 + 1;
        } 
        ptr = ptr + 1;   
    }
}

2.vector

(1)构造与初始化

std::vector<int> x1(3,1);
std::vector<int> x2{1,1,1};

(2)主要方法

    1获取元素个数,判断是否为空
std::cout << x1.size() << std::endl;
std::cout << x1.empty() << std::endl;
    2插入删除元素
x1.push_back(2);
x1.pop_back();
    3vector比较

(3)vector 中元素的索引与遍历

std::vector<int> x1 = {1, 2, 3};
std::cout << x1[2] << std::endl;
std::cout << x1.at(2) << std::endl;

(4)迭代器

    1模拟指针的行为

    2vector对应随机访问迭代器

        解引用与下标访问

        移动

        两个迭代器相减求距离

        两个迭代器比较

(5)其他内容

1添加元素可能使迭代器失效

2多维vector

std::vector<std::vector<int> > x;
x.push_back(std::vector<int>());
x[0].push_back(1);
std::cout << x[0][0] << std::endl;

3从 . 到 ->

std::vector<int>* ptr = &x;
std::cout << (*ptr).size() << std::endl;
std::cout << ptr->size() << std::endl;

4Member type

		size_type

        iterator/const_iterator

3.string

(1)构造与初始化

#include 
#include 

int main()
{
    std::string x("Hello");    //初始化
    std::string y("Hello World");

    std::cout<< (y < x) << std::endl;

    y = y + x;    //赋值
    std::cout << y << std::endl;
}

(2)方法

    1尺寸相关

    2比较

    3赋值

    4拼接

    5索引

    6转换为c字符串

第4章 表达式

1.左值与右值

(1) 所有的划分都是针对表达式的,不是针对对象或数值。

**泛左值 (glvalue)**(“泛化 (generalized)”的左值)是求值确定一个对象、位域或函数的个体的表达式。    可以理解为获取x所关联的内存
int x;
x = 3
**纯右值 (prvalue)**(“纯 (pure)”的右值)是求值符合下列之一的表达式:

    计算某个运算符的操作数的值(这种纯右值没有*结果对象*)
x = 3    //3为纯右值
    初始化某个对象(称这种纯右值有一个*结果对象*)
int x = 3;
**亡值 (xvalue)**(“将亡 (expiring)”的值)是代表它的资源能够被重新使用的对象或位域的泛左值。
#include 
#include 

void fun(std::vector<int>&& par)
{

}
int main()
{
    std::vector<int> x;
    fun(std::move(x));    //std::move(x)将x转化为将亡值

    //....后续不会再使用x,可对par重复利用
}

(2)左值与右值的转换

左值转换为右值( lvalue to rvalue conversion )
int x = 3;
int y;
y = x;
临时具体化( Temporary Materialization )prvalue to xvalue
struct Str
{
    int x;  
};
int main()
{
    Str();    //纯右值,不需关注内存
    Str().x; //Src()被视为将亡值,才可谈论内部的信息
}
void fun(const int& par)
{

}
int main()
{
    fun(3);    
}

(3)decltype

    decltype ( 表达式 )

a) 如果 表达式 的值类别是亡值,将会 decltype 产生 T&&

#include 
#include 
int main()
{
      int x;
    decltype(std::move(x)) y = std::move(x);
}

b) 如果 表达式 的值类别是左值,将会 decltype 产生 T&;#include

#include 

int main()
{
      int x;
    decltype((x)) y = x;
}
#include 

int main()
{
  int x;
  int & y = x;
  return 0;
}

c) 如果 表达式 的值类别是纯右值,将会 decltype 产生 T

#include 

int main()
{
    decltype(3) x;
}
#include 

int main()
{
  int x;
  return 0;
}

2.类型转换

(1)隐式类型转换

    自动发生

    实际上是一个(有限长度的)转型序列

    [隐式转换 - cppreference.com](https://zh.cppreference.com/w/cpp/language/implicit_conversion)

(2)显式类型转换(避免使用)

static_cast<double>(3) + 5;         
const int* ptr;
const_cast<int*>(ptr);
int x = 3;
double y = reinterpret_cast<double>(x);    //错误,无法编译


//正确写法,但结果可能不是想要的
int* ptr = &x;
double* ptr2 = reinterpret_cast<double*>(ptr);

3.算数运算符

1操作数与结果均为算数类型的右值;但加减法与一元 + 可接收指针。

2一元 + 或 - 操作符会产生 integral promotion
int main()
{
    short x = 3;
      auto y = -x;
}
int main()
{
  short x = 3;
  int y = +static_cast<int>(x);
  return 0;
}

4.逻辑与关系操作符

(1) 逻辑与、逻辑或具有短路特性

int main()
{
    int* ptr = nullptr;
    if (ptr && (*ptr == 3))    //ptr为空的话不会判断 *ptr是否为3
    {
    
    }
}

(2)逻辑与的优先级高于逻辑或

(3)不能将多个关系操作符串连

int a = 3;
int b = 4;
int c = 5;
std::cout << (c > b > a) << std::endl;
//c > b > a --> c > b true > a --> 1 > a --> false
//可以写成
std::cout << ((c > b) && (b > a)) << std::endl;

(4)不要写出 val == true 这样的代码

(5)Spaceship operator:<=>

    – strong_ordering
int main()
{
    int a = 5;
    int b = 4;
    auto res = (a <=> b);
    if (res > 0)    //也可写成 res == std::strong_ording::greater
    {

    }
    else if (res < 0)    //res == std::strong_ording::less
    {

    }
    else if (res = 0)    //res == std::strong_ording::equal
    {

    }
}
#include 

int main()
{
  int a = 5;
  int b = 4;
  std::strong_ordering res = (a <=> b);
  return 0;
}
    – weak_ordering
    – partial_ordering
#include 
#include 
#include 

int main()
{
    double f = 3.0;
    auto res (sqrt(-1) <=> 5.0);
    std::cout << (res > 0) << std::endl;    //0
    std::cout << (res < 0) << std::endl;    //0
    std::cout << (res == 0) << std::endl;   //0
    std::cout << (res == std::partial_ordering::unordering)
              << std::endl;    //1    
}

5.位操作符

接收右值,进行位运算,返回右值
● 除取反外,其它运算符均为左结合的
● 注意计算过程中可能会涉及到 integral promotion
● 注意这里没有短路逻辑
● 移位操作在一定情况下等价于乘(除) 2 的幂,但速度更快
● 注意整数的符号与位操作符的相关影响
– integral promotion 会根据整数的符号影响其结果
– 右移保持符号,但左移不能保证

6.赋值操作符

● 左操作数为可修改左值;右操作数为右值,可以转换为左操作数的类型
● 赋值操作符是右结合的
● 求值结果为左操作数
● 可以引入大括号(初始化列表)以防止收缩转换( narrowing conversion )
● 小心区分 = 与 ==
● 复合赋值运算符

7.自增自减运算符

● ++; –
● 分前缀与后缀两种情况
● 操作数为左值;前缀时返回左值;后缀时返回右值
● 建议使用前缀形式

8.其他运算符

(1)成员访问操作符: . 与 ->

  1  -> 等价于 (*).

  2  . 的左操作数是左值(或右值),返回左值(或右值 xvalue )

左值:

#include 

struct Str
{
  int x;

};

int main()
{
     Str a;
      decltype((a.x)) y = a.x;

      return 0;
}
#include 

struct Str
{
  int x;
  // inline Str() noexcept = default;
};

int main()
{
  Str a = Str();
  int & y = a.x;
  return 0;
}

右值

#include 

struct Str
{
  int x;

};

int main()
{
     Str a;
      decltype((Str().x)) y = std::move(a.x);
}
#include 

struct Str
{
  int x;
  // inline Str() noexcept = default;
};



int main()
{
  Str a = Str();
  int && y = std::move(a.x);
  return 0;
}
   3 -> 的左操作数指针,返回左值
#include 

struct Str
{
  int x;

};

int main()
{
     Str a;
      Str* ptr = &a;
      decltype((ptr->x)) y = a.x;
};
#include 

struct Str
{
  int x;
  // inline Str() noexcept = default;
};

int main()
{
  Str a = Str();
  Str * ptr = &a;
  int & y = a.x;
  return 0;
}

(2)条件操作符 ? :

    唯一的三元操作符

    接收一个可转换为 bool 的表达式与两个类型相同的表达式,只有一个表达式会被求值

    如果表达式均是左值,那么就返回左值,否则返回右值

    右结合
int main()
{
    int score = 100;
    int res = (score > 0) ? 1 : (score == 0) ? 0 : -1;
    std::cout << res << std::endl;
}

第5章 语句

1.基于范围的 for 循环(重要)

#include 
#include 

int main()
{
    std::vector<int> arr{1, 2, 3, 4, 5};
    for (int v : arr)
    {
        std::cout << v << "\n";
    }
}

使用常量左值引用读元素;

#include 
#include 

int main()
{
    std::vector<std::string> arr{"h", "e", "l"};
    for (const std::string& v : arr)
    {
        std::cout << v << "\n";
    }
}
#include 
#include 

int main()
{
    std::vector<std::string> arr{"h", "e", "l"};
    for (const auto& v : arr)
    {
        std::cout << v << "\n";
    }
}

使用 万能引用( “ universal reference )”修改元素

#include 
#include 

int main()
{
    std::vector<int> arr{1, 2, 3, 4, 5};
    for (auto& v : arr)
    {
        v = v + 1;
    }
}
#include 
#include 

int main()
{
    std::vector<int> arr{1, 2, 3, 4, 5};
    for (auto&& v : arr)        //万能引用
    {
        v = v + 1;
    }
}

2.达夫设备

处理无法整除的情形

额外增加一个循环语句

将 switch 与循环结合 ——达 夫设备
#include 
#include 

int main()
{
    constexpr size_t buffer_count = 10001;
    std::vector<size_t> buffer(buffer_count);
    for (stze_t i = 0; i < buffer_count; i++)
    {
        buffer[i] = i;
    }

    size_t max_value = buffer[0];
    auto ptr = buffer.begin();

    size_t i = 0
    switch (buffer_count % 8)
        for( ; i < (bufffer_count + 7) / 8; i++)
        {
        [[fallthrougth]];
        case 0: max_value = (max_value > *ptr) ? max_value : *ptr; ++ptr;
        [[fallthrougth]];
        case 7: max_value = (max_value > *ptr) ? max_value : *ptr; ++ptr;
        [[fallthrougth]];
        case 6: max_value = (max_value > *ptr) ? max_value : *ptr; ++ptr;
        [[fallthrougth]];
        case 5: max_value = (max_value > *ptr) ? max_value : *ptr; ++ptr;
        [[fallthrougth]];
        case 4: max_value = (max_value > *ptr) ? max_value : *ptr; ++ptr;
        [[fallthrougth]];
        case 3: max_value = (max_value > *ptr) ? max_value : *ptr; ++ptr;
        [[fallthrougth]];
        case 2: max_value = (max_value > *ptr) ? max_value : *ptr; ++ptr;
        [[fallthrougth]];
        case 1: max_value = (max_value > *ptr) ? max_value : *ptr; ++ptr;
        }
    std::cout << max_value << "\n";
}

第6章 函数

1.参数

(1)函数传值,传地址,传引用

(2)函数传参过程中的类型退化

#include 

void fun (int * par)
{

}

int main()
{
    int a[3];
    fun(a);
}

(3)变长参数

    initializer_list
#include 
#include 

void fun(initializer_list<int> par)
{

}

int main()
{
    fun({1, 2, 3, 4});
}
    可变长度模板参数

    使用省略号表示形式参数

(4)函数可以定义缺省实参

    如果某个形参具有缺省实参,那么它右侧的形参都必须具有缺省实参

    在一个翻译单元中,每个形参的缺省实参只能定义一次

    具有缺省实参的函数调用时,传入的实参会按照从左到右的顺序匹配形参

    缺省实参为对象时,实参的缺省值会随对象值的变化而变化

(5)main 函数的两个版本

    无形参版本

    带两个形参的版本
int main(int argc, char* argv[])
{

}

2.函数体

(1)函数返回

    隐式返回

    显式返回关键字: return

    返回值优化( RVO )—— C++17 对返回临时对象的强制优化
struct Str
{
    Str(int) {}
    Str(const Str&)
    {
        std::cout << "拷贝构造初始化\n"; 
    }
};
Str fun()
{
    Str x{3};
    return x;    //如果return x{3} 则C++17后强制优化
}

int main()
{
    Str res = fun();
}

(2)返回类型

1) 返回类型的几种书写方式

1 经典方法:位于函数头的前部
int fun(int a, int b)
{
    return a + b;
}
2 C++11 引入的方式:位于函数头的后部
auto fun(int a, int b) -> int
{
    return a + b;
}
3 C++14 引入的方式:返回类型的自动推导
int fun(int a, int b)
{
    return a + b;
}
          使用 constexpr if 构造 具有不同返回类型 的函数
constexpr bool value = true;
auto fun()
{
    if constexpr(value)
    {
        return 1;
    }
    else
    {
        return 3.14;
    }
}
2)返回类型与结构化绑定( C++ 17 )
#include 

struct Str
{
    int x;
    int y;
};
Str fun()
{
    return Str{};
}
int main()
{
    auto [v1, v2] = fun();
    v1;
    v2;
}
#include 

struct Str
{
  int x;
  int y;
};

Str fun()
{
  return Str{0, 0};
}

int main()
{
  Str __fun15 = fun();
  int & v1 = __fun15.x;
  int & v2 = __fun15.y;
  v1;
  v2;
  return 0;
}
3)[[nodiscard]] 属性( C++ 17 )
#include 
[[nodiscard]] int fun(int a, int b)
{
    return a + b;
}

int main()
{
    fun(2, 3);
    //int x = fun(2, 3);
    //std::cout << x << std::endl;
}

3.函数重载与重载解析

(1)函数重载

(2)名称查找

    限定查找( qualified lookup )
#include 
void fun()
{
    std::cout << "global fun is called.\n";
}
namespace MyNs
{
    void fun()
    {
        std::cout << "MyNs fun is called.\n";
    }
}

int main()
{
    ::fun();
    MyNs::fun();
}
  非限定查找( unqualified lookup )

    非限定查找会进行域的逐级查找 名称隐藏( —— hiding )
#include 
void fun()
{
    std::cout << "global fun is called.\n";
}
namespace MyNs
{
    void fun()
    {
        std::cout << "MyNs fun is called.\n";
    }

    void g()
    {
        fun();
    }
}

int main()
{
    MyNs::g();
}
    实参依赖查找( Argument Dependent Lookup: ADL )

        只对自定义类型生效
namespace MyNs
{
    struct Str{};
    void g(Str x)
    {

    }
}
int main()
{
    MyNs::Str obj;
    g(obj);
}

(3)重载解析

    在名称查找的基础上进一步选择合适的调用函数

    1过滤不能被调用的版本

            参数个数不对

            无法将实参转换为形参

            实参不满足形参的限制条件
void fun(int x)
{

}
void fun(std::string x)
{

}
int main()
{
    fun(3);
}
        2在剩余版本中查找与调用表达式最匹配的版本,匹配级别越低越好(有特殊规则)

            级别 1 :完美匹配 或 平凡转换(比如加一个 const )

            级别 2 : promotion 或 promotion 加平凡转换(如short -> int)

            级别 3 :标准转换 或 标准转换加平凡转换(如int -> double)

            级别 4* :自定义转换 或 自定义转换 加平凡转换 或 自定义转换加标准转换(与类相关)

            级别 5* :形参为省略号的版本

            函数包含多个形参时,所选函数的所有形参的匹配级别都要优于或等于其它函数
void fun(int x, int y)
{

}
void fun(int x, double y)
{

}
void fun(bool x, float y)    //1, 3
{

}
void fun(int x, double y)    //2, 1
{

}
int main()
{
    fun(true, 1.0);        //有歧义
    fun(true, 1.0f);
    fun(static_cast<int>(true, 1.0);
}

4.函数相关的其它内容

(1)递归函数

    通常用于描述复杂的迭代过程

(2)内联函数

    通常在.h文件中定义内联函数,使得函数从程序级别的一处定义转化为翻译单元的一次定义。

(3)constexpr 函数 (C++11 起 )

#include 
constexpr int fun()    //函数可在编译期执行,也可在运行期执行
{
    return 3;
}
constexpr int x = fun();
int main()
{

}
    consteval 函数(C++20起)

        只能在编译器求值

(4)函数指针

    函数指针类型
int main()
{
    //引入:数组的指针类型
    using K = int[3];
    K * a;     //等价于int (*a) [3],a是一个指针,指向包含3个int的数组
    int * a[3] //a是一个数组,包含3个元素,每个元素是int* 类型的对象     
}
int inc(int x)
{
    return x + 1;
}

int dec(int x)
{
    return x + 1;
}
using K = int(int);
int main()
{
    K * fun = &inc;
    std::cout << (*fun)(100) << std::endl;
}
int inc(int x)
{
    return x + 1;
}

int dec(int x)
{
    return x + 1;
}
using K = int(int);
int Twice(K * fun, int x)
{
    int temp = (*fun)(x);
    return temp * 2;
}
int main()
{
    std::cout << Twice(&inc, 100) << std::endl;
    std::cout << Twice(&dec, 100) << std::endl;
}
    函数指针与重载
void fun(int)
{

}
/*
void fun(int, int)
{

}
*/
int mian()
{
    auto x = fun;    //退化为函数指针类型
}
void fun(int)
{

}
void fun(int, int)
{

}
int mian()
{
    //auto x = fun;    //两个不同的fun函数类型,无法隐式转换
    using K = void(int);
    K * x = fun;
}
    将函数指针作为函数参数

    将函数指针作为函数返回值
int inc(int x)
{
    return x + 1;
}

int dec(int x)
{
    return x + 1;
}

auto fun(bool input)
{
    if (input)
        return inc;
    else
        return dec;
}
int main()
{
    std::cout << (*fun(true))(100) << std::endl;
}

你可能感兴趣的:(C++,c++,开发语言)