跳转至

C++

1 数据类型

1.1 基本类型占字节数

基本数据类型

类型说明符 等价类型 按数据模型的位宽
C++ 标准 LP32 ILP32 LLP64 LP64
short
short int 至少 16 16 16 16 16
short int
signed short
signed short int
unsigned short
unsigned short int
unsigned short int
int
int 至少 16 16 32 32 32
signed
signed int
unsigned
unsigned int
unsigned int
long
long int 至少 32 32 32 32 64
long int
signed long
signed long int
unsigned long
unsigned long int
unsigned long int
long long
long long int
(C++11)
至少 64 64 64 64 64
long long int
signed long long
signed long long int
unsigned long long
unsigned long long int
(C++11)
unsigned long long int

1.2 POD(Plain Old Data)简单旧数据

1.3 TrivialType 平凡类型

1.4 聚合体

聚合体是下列类型之一:

  • 数组类型
  • 符合以下条件的类类型(常为 struct 或 union)
  • 没有私有或受保护的直接 (C++17 起) 非静态数据成员
  • 没有用户声明的构造函数 (C++11 前)
  • 没有用户提供的构造函数(允许显式预置或弃置的构造函数)(C++11 起)(C++17 前)
  • 没有用户提供、继承或 explicit 的构造函数(允许显式预置或弃置的构造函数)(C++17 起)(C++20 前)
  • 没有用户声明或继承的构造函数 (C++20 起)
  • 没有虚、私有或受保护 (C++17 起) 的基类
  • 没有虚成员函数
  • 没有默认成员初始化器 (C++11 起)(C++14 前)

1.5 位域

  • 参考: https://learn.microsoft.com/zh-cn/cpp/cpp/cpp-bit-fields?view=msvc-170
  • 定义:声明具有以位为单位的明确大小的类数据成员。相邻的位域成员可以打包成共享和跨过各个字节。
  • 语法:
  • 类型 标识符(可选) 属性(可选) : 大小
  • 类型 标识符(可选) 属性(可选) : 大小 花括号或等号初始化器 (C++20 起)
  • 规则:
  • 规则 1:位域的类型只能是整型或枚举类型。
  • 规则 2:位域不能是静态数据成员。
  • 分类
  • 有名位域:有标识符(变量名)
  • 无名位域:没有标识符(变量名),大小起到一个填充位的作用
  • 位域大小:只能是 0 或大于 0 的整数
  • 等于 0:只能用在无名位域中,表示下个位域应该在分配单元的边界开始。
  • 大于 0:表示位域将占有的位数
#include <iostream>

struct S1
{
    // 三位的无符号位域,允许的值有 0...7
    unsigned int b : 3;
};
struct S2
{
    // 通常会占用 2 个字节:
    // 3 位:b1 的值
    // 2 位:不使用
    // 6 位:b2 的值
    // 2 位:b3 的值
    // 3 位:不使用
    unsigned char b1 : 3, : 2, b2 : 6, b3 : 2;
};
struct S3
{
    // 通常会占用 2 个字节:
    // 3 位:b1 的值
    // 5 位:不使用
    // 6 位:b2 的值
    // 2 位:b3 的值
    unsigned char b1 : 3;
    unsigned int :0; // 开始新字节,边界大小4
    unsigned char b2 : 3;//从第5字节开始
    unsigned char :0; // 开始新字节,边界大小1
    unsigned char b3 : 1;//从第6字节开始
};
int main()
{
    S1 s = {0b00000111};
    std::cout << s.b << '\n';// 输出 7
    s.b=0b00001101; // 超过3bit的值存储不下
    std::cout << s.b << '\n'; // 输出5,(0b00000101)正式来说是由实现定义的行为

    std::cout << sizeof(S2) << '\n'; // 输出 2

    std::cout << sizeof(S3) << '\n'; // 输出 6
}

2 指针

2.1 指针概述

指针 p 是也是一个变量,变量 p 存的是一段地址数据(地址可能 4 位,也可能 8 位),*p 的作用是得到 p 的值(地址)所指向的数据,&p 可以获得变量 p 的地址。

  • 指针是变量:指针是一种特殊的变量,有自己的地址和值,与普通变量唯一不同是存储的值是地址。
  • 指针存的是地址
  string* str = new string("你好");
    cout << "new a string object :" << endl;
    cout << "指针str地址:"<<&str<<"指针str值:"<<str<<"指针str值指向的值:"<<*str<<endl;
  • 可能的地址值如下
地址 变量名
0F001100 pstr 0F001110
0F001110 *pstr Hello World
&pstr 0F001100
0F001200 str Hello World

2.1.1 指针的指针 int** pp

2.1.2 delete 指针后指针赋值 null?

2.2 const 和指针的关系

注:以下举例以 int 作为类型, 且 new 的数据未作 delete 处理

2.2.1 速记技巧

const int* p;//const指向的是*p,*p就是p指向的数据,故指向数据不能改变
int const* p;//同上
int* const p=NULL;//const指向的是p,p是指针,故p不能改变,但p指向数据可以改变

2.2.2 类型一:const 和 int

//类型一
    const int i1 = 2;//【Ⅰ】
    // i1 = 3;//error

    int const i2 = 2;//【Ⅱ】等价于【Ⅰ】
    //i2 = 3;//error 

2.2.3 类型二:const 和 int 和 *

    int* tpi = new  int(1);
    int** tppi = new int* (new int(1));

    //类型二
    const int* pi_11{ new int(2) };//【Ⅲ】pi_11指向的值为常量
    //(*pi_11) = 3;//error
    pi_11 = tpi;//success

    int const* pi_12{ new int(2) };//【Ⅳ】等价于【Ⅲ】
    //(*pi_12) = 3;//error
    pi_12 = tpi;//success
    *tpi = 3;
    int* const pi_13{ new int(2) };//Ⅴ】pi_13指针为常量
    (*pi_13) = 3;//success
    //pi_13 = tpi;//error

    const int* const pi{ new int(2) };//【Ⅵ】pi指针为常量,且pi指向的值也为常量
    //pi = tpi;//error
    //(*pi) = 3;//error

2.2.4 类型三:const 和 int 和 **

    int* tpi = new  int(1);
    int** tppi = new int* (new int(1));
    const int* pi_11{ new int(2) };
    //类型三
    int* const* const ppi1{ new int* (new int(2)) };//【Ⅶ】ppi1指针为常量,且ppi1指针的指针为常量
    //ppi1 = tppi;//error
    //(*ppi1) = tpi;//error
    (*(*ppi1)) = 3;//success

    const int* const* const ppi2{ new int* (new int(2)) };//【Ⅷ】ppi2指针为常量,且ppi2指针的指针为常量,且ppi2指针的指针指向的值也为常量
    //ppi2 = tppi;//error
    //(*ppi2) = tpi;//error
    //(*(*ppi2)) = 3;//error
    int const * const* const ppi3{ new int* (new int(2)) };//【Ⅸ】等价于【Ⅷ】

    int** const ppi4{ new int* (new int(2)) };//【Ⅹ】ppi5二级指针为常量
    //ppi4 = tppi;//error
    (*ppi4) = tpi;//success
    (*(*ppi4)) = 2;//sucess

    const int** ppi5{ new const int* (new int(2)) };//【ⅩⅠ】ppi5指针的指针指向的值为常量
    ppi5 = &pi_11;//success
    (*ppi5) = pi_11;//success
    //(*(*ppi5)) = 2;//error

    int const** ppi6{ new const int* (new int(2)) };//【ⅩⅡ】等价于【ⅩⅠ】

2.3 c++ 引用和指针运用多态特性

2.3.1 指针方式

将子类对象赋给基类指针

class A
{
public:
    virtual void operator ()()
    {
        cout<< "A";
    };
};
class B:public A {
    virtual void operator ()()
    {
        cout << "B";
    };
};
int main()
{
    A* aP = new B();
    (*aP)();
    delete aP;
    aP = nullptr;
}

上面代码执行(*aP)();之后打印出 B,而不是 A, 说明是执行了子类方法。

2.3.2 引用方式

将子类对象赋给基类引用

class A
{
public:
    virtual void operator ()()
    {
        cout<< "A";
    };
};
class B:public A {
    virtual void operator ()()
    {
        cout << "B";
    };
};
int main()
{
    B b;
    A& aRef = b;
    aRef();
}

上面代码执行aRef ();之后打印出 B,而不是 A,说明是执行了子类方法。

2.4 指针引用

  • *& :指针的引用
  • **& :指针的指针的引用 当指针和引用同时出现是,表示的是对指针的引用(不会存在对引用的指针),并且*要在&之前。
    int* &ptrRef = someIntPtr; // ptrRef 是一个指向整数指针的引用
    static void * & nextof(void * const ptr)
    {
      return *(static_cast<void **>(ptr));
    }
    

3 数组

3.1 二维数组

二维数组可以从栈中创建

int m=10,n=5;
int arr[m][n];//创建10行5列数组

也可以从堆中创建

int m=10,n=5;
int ** arr;
arr=new int* [m];//二维数组实质上是一个数组元素是另一个数组,而数组可用指针替换
for(int i=0;i<m;++i)
{
    arr[i]=new int[n];//每一个数组元素项都是一个数组
}

从堆中释放

for(int i=0;i<m;++i)
{
    delete[] arr[i];//删除内部数组
}
delete[] p;//删除外层数组

4 类

4.1 类成员变量

4.1.1 初始化

类成员变量有 3 中初始化方式: 1. 声明时使用=或者 {} 初始化(C++11) 2. 初始化列表 3. 构造函数初始化 - 示例

class A
{
public:
    // 1.声明时初始化
    int a = 1;
    // 2.初始化列表
    A(int a_):a(a_){}
    // 3.构造函数初始化
    A(int a_, bool b){ a = a_; }
};

4.1.2 位域

  • https://zh.cppreference.com/w/cpp/language/bit_field 声明具有以位为单位的明确大小的类数据成员。相邻的位域成员可以打包成共享和跨过各个字节。 位域是一种可以比 bit 级别的数据类型,只能用在类、结构体中,可以仅占用某些 bit 位,而不是多个 byte。
  • 声明规则:
标识符 (可选) 属性 (可选) : 大小   (1) 
标识符 (可选) 属性 (可选) : 大小花括号或等号初始化器 (2) (C++20 起)


属性  -   (C++11 起) 任意数量属性的序列
标识符 -   被声明的位域名。名字是可选的:无名位域引入指定数量的填充位。
大小  -   值大于或等于零的整型常量表达式。大于零时,这是位域将占有的位数。零值只能用于无名位域并具有特殊含义。
  • 限制要求:
  • 位域的类型只能是整型或(可有 cv 限定的)枚举类型,无名位域不能声明为具有有 cv 限定的类型。
  • 位域不能是静态数据成员。
  • 示例
#include <iostream>
 
struct S
{
    // 三位的无符号位域,允许的值有 0...7
    unsigned int b : 3;
};
 
int main()
{
    S s = {6};
 
    ++s.b; // 在位域中存储值 7
    [std::cout << s.b << '\n';
 
    ++s.b; // 值 8 不适合此位域
    std::cout << s.b << '\n'; // 正式来说是由实现定义的行为,通常为 0
}

4.2 重写、重载和隐藏

1.重写(override)的意思更接近覆盖,在 C++ 中是指派生类覆盖了基类的 虚函数,这里的覆盖必须满足有相同的函数签名和返回类型,也就是说有相同的函数名、形参列表以及返回类型。 2.重载(overload),它通常是指在 同一个类中 有两个或者两个以上函数,它们的 函数名相同,但是函数签名不同,也就是说有不同的形参。这种情况在类的构造函数中最容易看到,为了让类更方便使用,我们经常会重载多个构造函数。 3.隐藏(overwrite)的概念也十分容易与上面的概念混淆。隐藏是指基类成员函数,无论它是否为虚函数,当派生类出现同名函数时,如果派生类函数签名不同于基类函数,则基类函数会被隐藏。如果派生类函数签名与基类函数相同,则需要确定基类函数是否为虚函数,如果是虚函数,则这里的概念就是重写;否则基类函数也会被隐藏。另外,如果还想使用基类函数,可以使用 using 关键字将其引入派生类。

4.2.1 使基类同名不同参函数不隐藏

在 C++中,如果你希望派生类中的同名函数不隐藏基类中的同名函数(即使参数列表不同),可以使用以下几种方法:

4.2.1.1 使用 using 声明

using 声明可以将基类的成员函数引入到派生类的可见范围内,从而避免函数隐藏。

#include <iostream>

class Base {
public:
    void display() {
        std::cout << "Base display()" << std::endl;
    }

    void display(int x) {
        std::cout << "Base display(int x): " << x << std::endl;
    }
};

class Derived : public Base {
public:
    using Base::display;  // 将基类的 display 函数引入到派生类中

    void display() {
        std::cout << "Derived display()" << std::endl;
    }
};

int main() {
    Derived d;
    d.display();  // 调用 Derived::display()
    d.display(10);  // 调用 Base::display(int)

    return 0;
}
4.2.1.2 显式调用基类函数

在派生类中显式调用基类的函数,可以避免函数隐藏。

#include <iostream>

class Base {
public:
    void display() {
        std::cout << "Base display()" << std::endl;
    }

    void display(int x) {
        std::cout << "Base display(int x): " << x << std::endl;
    }
};

class Derived : public Base {
public:
    void display() {
        std::cout << "Derived display()" << std::endl;
    }

    void display(int x) {
        Base::display(x);  // 显式调用基类的 display(int) 函数
    }
};

int main() {
    Derived d;
    d.display();  // 调用 Derived::display()
    d.display(10);  // 调用 Derived::display(int),实际上调用 Base::display(int)

    return 0;
}
4.2.1.3 使用模板(C++11 及以上)

在某些情况下,可以使用模板来避免函数隐藏。

#include <iostream>

class Base {
public:
    void display() {
        std::cout << "Base display()" << std::endl;
    }

    void display(int x) {
        std::cout << "Base display(int x): " << x << std::endl;
    }
};

template <typename Base>
class Derived : public Base {
public:
    using Base::display;  // 将基类的 display 函数引入到派生类中

    void display() {
        std::cout << "Derived display()" << std::endl;
    }
};

int main() {
    Derived<Base> d;
    d.display();  // 调用 Derived::display()
    d.display(10);  // 调用 Base::display(int)

    return 0;
}

4.3 委托构造函数(C++11)

  • 解释:某个类型的一个构造函数可以委托同类型的另一个构造函数对对象进行初始化。构造函数,它既是委托构造函数也是代理构造函数。
  • 委托构造函数执行顺序:先执行代理构造函数的初始化列表,接着执行代理构造函数的主体,最后执行委托构造函数的主体。

示例:

class X  
{  
public:  
X() : X(0, 0.) {}  
X(int a) : X(a, 0.) {}  
X(double b) : X(0, b) {}  
X(int a, double b) : a_(a), b_(b) { CommonInit(); }  
private:  
void CommonInit() {}  
int a_;  
double b_;  
};

限制条件:如果一个构造函数为委托构造函数,那么其初始化列表里就不能对数据成员和基类进行初始化。

class X  
{  
public:  
X() : a_(0), b_(0) { CommonInit(); }  
X(int a) : X(), a_(a) {} // 编译错误,委托构造函数不能在初始化列表初  
始化成员变量  
X(double b) : X(), b_(b) {}// 编译错误,委托构造函数不能在初始化列表初  
始化成员变量  
private:  
void CommonInit() {}  
int a_;  
double b_;  
};

4.3.1 为什么委托构造函数其初始化列表里就不能对数据成员和基类进行初始化

在 C++中,委托构造函数(Delegating Constructor)是一种特殊的构造函数,它通过调用同一个类的另一个构造函数来完成初始化工作。委托构造函数的主要目的是减少代码重复,提高代码的可维护性。

4.4 委托构造函数的语法

委托构造函数的语法如下:

class MyClass {
public:
    MyClass(int x, int y) : x_(x), y_(y) {}  // 目标构造函数

    MyClass() : MyClass(0, 0) {}  // 委托构造函数

private:
    int x_;
    int y_;
};

在这个例子中,MyClass() 是委托构造函数,它通过调用 MyClass(int x, int y) 来完成初始化工作。

4.5 为什么不能在委托构造函数的初始化列表中对数据成员和基类进行初始化

在委托构造函数的初始化列表中,只能调用同一个类的其他构造函数,而不能对数据成员和基类进行初始化。这是因为:

  1. 初始化顺序
  2. 在 C++中,构造函数的初始化列表中的初始化顺序是按照类定义中成员的声明顺序进行的。
  3. 委托构造函数的初始化列表中只能包含对其他构造函数的调用,而不能包含对数据成员和基类的初始化。

  4. 避免重复初始化

  5. 如果允许在委托构造函数的初始化列表中对数据成员和基类进行初始化,可能会导致重复初始化的问题。
  6. 例如,如果委托构造函数和目标构造函数都对同一个数据成员进行初始化,可能会导致不确定的行为。

  7. 语法限制

  8. C++标准规定,委托构造函数的初始化列表中只能包含对其他构造函数的调用,而不能包含对数据成员和基类的初始化。
4.5.1.1 示例代码
#include <iostream>

class Base {
public:
    Base(int x) : x_(x) {}

private:
    int x_;
};

class Derived : public Base {
public:
    Derived(int x, int y) : Base(x), y_(y) {}  // 目标构造函数

    Derived() : Derived(0, 0) {}  // 委托构造函数

private:
    int y_;
};

int main() {
    Derived d;
    return 0;
}
4.5.1.2 解释
  1. 目标构造函数
  2. Derived(int x, int y) 是目标构造函数,它负责初始化基类 Base 和数据成员 y_

  3. 委托构造函数

  4. Derived() 是委托构造函数,它通过调用 Derived(int x, int y) 来完成初始化工作。
  5. 在委托构造函数的初始化列表中,只能包含对其他构造函数的调用,而不能包含对数据成员和基类的初始化。

4.5.2 委托模板构造函数

在类的模板构造函数中同样可以使用委托构造。

#include <vector>  
#include <list>  
#include <deque>  
class X {  
  template<class T> X(T first, T last) : l_(first, last) { }  
  std::list<int> l_;  
public:  
  X(std::vector<short>&);  
  X(std::deque<int>&);  
};  
X::X(std::vector<short>& v) : X(v.begin(), v.end()) { }  
X::X(std::deque<int>& v) : X(v.begin(), v.end()) { }  
int main()  
{  
  std::vector<short> a{ 1,2,3,4,5 };  
  std::deque<int> b{ 1,2,3,4,5 };  
  X x1(a);  
  X x2(b);  
}

4.6 使用 using 继承构造函数或成员函数

详见本页面 using 部分。

#include<stdio.h>
class Base {
public:
void foo(int) {printf("Base");}
};
class Derived : public Base {
public:
using Base::foo;  // 注释此行后,d.foo(5)将输出Derived,打开后输出Base
void foo(double) {printf("Derived");}
};
int main()
{
Derived d;
d.foo(5);
}

上面示例会输出 Base,但如果注释掉第 8 行 using Base::foo; 则输出 Derived,原因是派生类和基类具有 同名函数 foo,且具有 相同数量的参数 列表,那么派生类会隐藏基类同名函数。

4.7 嵌套类

4.7.1 嵌套类声明定义

嵌套类有 2 中写法: 1. 在类中声明,类外定义 2. 在类中声明时定义 - 示例

class A
{
public:
    // 1.嵌套类在类中声明
    class A1;
    // 2.类中声明时定义
    class A2{};
};
// 1.嵌套类在类外定义
class A::A1
{
public:
    A();
    ~A(){}
};
// 类构造定义
A::A1::A1(){}

4.8 类设计陷阱

4.8.1 1. 类成员是一个基类指针,深拷贝避免丢失子类数据

class A
{
public:
    virtual ~A(){}
public:
    string str_A;
};
class B :public A
{
public:
    string str_B;
};
class C
{
public:
    C() :m_a(nullptr) 
    {

    }
    C(const C& in)
    {
        if (in.m_a != nullptr)
        {
            m_a = new A();
            *m_a = *in.m_a;
        }
        else
           m_a = nullptr; 

    }
    C& operator=(const C& in)
    {
        if (&in == this)
            return *this;
        if (m_a != nullptr)
            delete m_a;
        if (in.m_a != nullptr)
        {
            m_a = new A();
            *m_a = *in.m_a;
        }
        else
            m_a = nullptr; 
        return *this;
    }
public:
    A* m_a;
};
  • 问题分析:上面例子中看似很完美,对 C 中的指针 m_a 进行了深拷贝,然而 m_a 失去了对子类的数据拷贝。当 m_a 是 B 指针赋给 A 指针时,这时拷贝构造和赋值运算符中 *m_a = *in.m_a; 只会去调 A 的赋值运算符,而不会去调 B 的,这样,m_a 中 B 数据就丢失了。
  • 解决方法:通过 static_cast 或 dynamic_cast 判断具体子类类型, 在对子类赋值(调用子类的赋值运算符)。
class C
{
public:
    C() :m_a(nullptr) 
    {

    }
    C(const C& in)
    {
        if (in.m_a != nullptr)
        {
            if (dynamic_cast<B*>(in.m_a))
            {
                B* pb = new B(*dynamic_cast<B*>(in.m_a));
                m_a = pb;
            }
            else
            {
                m_a = new A;
                *m_a = *in.m_a;
            }
            /*  m_a = new A();
              *m_a = *in.m_a;*/
        }
        else
            m_a = nullptr;

    }
    C& operator=(const C& in)
    {
        if (&in == this)
            return *this;
        if (m_a != nullptr)
            delete m_a;
        if (in.m_a != nullptr)
        {
            if (dynamic_cast<B*>(in.m_a))
            {
                B* pb = new B(*dynamic_cast<B*>(in.m_a));
                m_a = pb;
            }
            else
            {
                m_a = new A;
                *m_a = *in.m_a;
            }
            /* m_a = new A();
             *m_a = *in.m_a;*/
        }
        else
            m_a = nullptr;

        return *this;
    }
    ~C()
    {
        if (m_a != nullptr)
        {
            delete m_a;
            m_a = nullptr;
        }
    }
public:
    A* m_a;
};

4.8.2 2. 类成员指针变量设计成私有,避免疏忽导致的内存泄漏

class A
{

};
class B
{
public:
    B() :m_a(nullptr) {}
    ~B() 
    {
        if (m_a != nullptr)
        {
            delete m_a;
            m_a = nullptr;
        }
    }
public:
    A* m_a;
};
int main()
{
    B b;
    {
        A* a1 = new A;
        b.m_a = a1;
    }
    {
        A* a2 = new A;
        b.m_a = a2;
    }

}
  • 问题分析:上面 B 类定义看似完美,B 生命周期结束自动删除 m_a 内存, 但不正当的给 m_a 赋值导致了意外的内存泄漏。main 函数中 a1 首先赋给了 b.m_a, 意图将 a1 的控制权交给 b,确实 b 接管了 a1,当 b 生命周期结束也可以通过析构删除 a1 指针, 当 a2 又赋给 b.m_a 时,m_a 痛快的结束了 a2,从而放弃了 a1 的控制权, 使 a1 内存泄漏。
  • 解决方法:将成员指针设为私有,访问通过成员函数。
class A
{

};
class B
{
public:
    B() :m_a(nullptr) {}
    ~B()
    {
        if (m_a != nullptr)
        {
            delete m_a;
            m_a = nullptr;
        }
    }
    void setValue(A* a)
    {
        if (m_a != nullptr)
        {
            delete m_a;
            m_a = nullptr;
        }
        m_a = a;
    }
    A* getValue()
    {
        return m_a;
    }
private:
    A* m_a;
};
int main()
{
    B b;
    {
        A* a1 = new A;
        b.setValue(a1);
    }
    {
        A* a2 = new A;
        b.setValue(a2);
    }

}

4.8.3 类中成员变量为类自身

类中成员变量为类自身时怎么写?

4.8.3.1 1. 指针,智能指针可以,对象不可以

先看以下代码,一般成员声明情况

class A
{
public:
    A();
    ~A();
    A* m_pa; //正常
    A m_a;   //error:uses 'B', which is being define   
    shared_ptr<A> m_spa; //正常
};

解释: 1. A m_a; 不能通过编译,由错误信息可知,m_a 实例化时类 A 正在定义中(即还没有定义),编译器无法知道类 A 占多少空间,因此不能编译通过。 2. A* m_pa;和 shared_ptr<A> m_spa; 能正常通过编译,因为 m_pa 是指针存的是地址,只占 4 字节大小,可以明确确定。

4.8.3.2 2. 指针和智能指针怎么赋值?
  1. 不能在对象未生成时执行对象的赋值操作
class A
{
public:
    A(){m_pa = new A;}
    A(int){}
    A(char) { init(); }
    ~A();
    A* m_pa;
    shared_ptr<A> m_spa;
    void init() {
        m_pa = new A;
        m_spa.reset(m_pa);
    }
};

A a1; //error调用A();
A a2(2);//正常;
A a2('a');//error调用A(char);
a2.init();//正常

解释 2. A a1;会报错是因为 A 调用构造 A () 时,执行了语句 m_pa = new A; 而此时 A 的大小未知 new 多少内存无法确定,故报错; 3. A a2 (2); 正常执行,因为他执行了构造 A (int){},未对 m_pa 操作; 4. A a2 ('a'); 会报错是因为构造 A (char) 间接调用对 m_pa 的赋值操作,理由与 1 一样; 5. a2.init (); 正常执行是因为此时 A 的大小已知,可以执行赋值操作 6. 智能指针遵循指针一样的原则

总结:指针成员不能在类实例化阶段直接或间接进行操作

4.9 重载运算符

#include<iostream>
using namespace std;
struct S
{
    int val;
    S(const S& s):val(s.val){}
    S():val(0){}
    S(int i) :val(i) {}
    S& operator =(const S& s)
    {
        if (&s == this)
            return *this;
        val = s.val;
        return *this;
    }
    //++前置
    S operator ++(){
        printf("前置++");
        ++val;
        return *this;
    }
    //后置++
    S operator ++(int){
        printf("后置++");
        S s(*this);
        ++val;
        return s;
    }
    //转换函数
    operator int&()
    {
        return val;
    }//S对象转换int引用
    operator int()
    {
        return val;
    }//S对象转换int值
    operator int*()
    {
        return &val;
    }//S对象转换int*
    friend ostream& operator << (ostream& output,const S & s)
    {
        output << s.val;
        return output;
    }
    bool operator <(const S& s)
    {
        return val < s.val ? true:false;
    }
    void operator()(int i)//函数对象,参数,返回值任意
    {
        val = i;
    }
    int& operator[](int i)//此类不含数组类型,故设计直接返回了val成员
    {
        return val;
    }
    void* operator new(size_t t)// 注意函数的第一个参数和返回值都是固定的
    {
        //return malloc(t);
        return ::operator new(t);
    }
    void operator delete(void* ptr) // 重载了new就需要重载delete
    {
        //free(ptr);
        ::operator delete(ptr);
    }  
};

4.10 类设计常识

4.10.1 1. 类中默认 8 个函数

class A
{
public:
    // 默认构造函数;
    A();
    // 默认拷贝构造函数
    A(const A&);
    // 默认析构函数
    ~A();
    // 默认重载赋值运算符函数
    A& operator = (const A&);
    // 默认重载取址运算符函数
    A* operator & ();
    // 默认重载取址运算符const函数
    const A* operator & () const;
    // 默认移动构造函数
    A(A&&);
    // 默认重载移动赋值操作符
    A& operator = (const A&&);
};

4.10.2 1. 声明了任何构造函数,编译器不会提供默认构造函数

  • 分析:以下代码中自定义了 Box 的构造,将会使默认构造函数 Box(){} 失效, 因此代码 Box box3 将显式失败。
class Box {
public:
    Box(int width, int length, int height)
        : m_width(width), m_length(length), m_height(height){}
private:
    int m_width;
    int m_length;
    int m_height;

};

int main(){

    Box box1(1, 2, 3);
    Box box2{ 2, 3, 4 };
    Box box3; // C2512: no appropriate default constructor available
}

4.10.3 提供 Init 成员函数

  1. 如果类中需要加载类外功能模块,需要定义一个 Init 成员函数,将初始化(加载)步骤在 Init 函数内实现,暴露 public,供使用时主动调用,这样,能根据 Init 函数的返回值判断结果;如果放在构造中,我们并不能知道这些模块是否加载成功。
  2. 构造函数里面只针对成员变量赋默认值(通常为 0 值、NULL 值)。

5 类型转换

  • static_cast :适用于例如 short 转 int、int 转 double 或 void*T* 转换
  • dynamic_cast :适用于含类继承关系的指针引用转换
  • reinterpret_cast :从二进制位重新解释类型的转换,可以将 2 个毫无关联的类型进行转换
  • bit_cast :从二进制位重新解释类型的转换,但转换前后对象都含有相同的 bit 位,如(4 位 float 可以转 4 位 int,但不能转 8 位 long)
  • 示例
#include <bit>
int main()
{
    using namespace std;
    double f = 1.23;
    int i_s = static_cast<int>(f);//i=1
    //reinterpret_cast只能转&或*
    int i_r1 = reinterpret_cast<int&>(f);//i_r1=2061584302
    int i_r2 = (*reinterpret_cast<int*>(&f));//i_r2=2061584302

    //reinterpret_cast和bit_cast比较
    long long l_r = reinterpret_cast<long long&>(f); //l_r=4608218246714312622
    long long l_b = bit_cast<long long>(f);//l_b=4608218246714312622
    //double d = bit_cast<double>(i_r1);//error,4位int不能转8位double
}

6 初始化

  • 参考
  • 初始化语法
  • ( 表达式列表 ) : 括号中的以逗号分隔的含有任意表达式和花括号初始化器列表的列表
  • = 表达式 : 等号后面跟着一个表达式
  • { 初始化器列表 } : 花括号初始化器列表:以逗号分隔且可以为空的含有表达式和其他花括号初始化器列表的列表
  • 初始化类型有:
  • 值初始化,例如 std::string s{};
  • 直接初始化,例如 std::string s("hello");
  • 复制初始化,例如 std::string s = "hello";
  • 列表初始化,例如 std::string s{'a', 'b', 'c'};
  • 聚合初始化,例如 char a[3] = {'a', 'b'};
  • 引用初始化,例如 char& c = a[0];

6.1 值初始化 (C++03 起)

6.2 直接初始化、复制初始化

  • 直接初始化语法:
T 对象 ( 实参列表 );
T 对象 { 实参列表 };      (C++11 )
T ( 实参列表 )
static_cast< T >( 其他对象 )
new T(实参列表)
::() : 成员(实参列表) { ... }
[实参](){ ... }       (C++11 )
  • 复制初始化语法:
目标类型 对象 = 其他对象; 
目标类型 对象 = {其他对象} ;  (C++11 )
函数(其他对象)
return 其他对象;
throw 对象;
catch (目标类型 对象)
目标类型 数组[元素个数] = {其他对象序列};

6.3 列表初始化

  • 定义:从 {} 列表初始化对象
  • 直接列表初始化
T 对象 { 实参1, 实参2, ... }; (1) 
T { 实参1, 实参2, ... } (2) 
new T { 实参1, 实参2, ... } (3) 
类 { T 成员 { 实参1, 实参2, ... }; };  (4) 
类::类() : 成员{实参1, 实参2, ...} {... (5) 
  • 复制列表初始化
T 对象 = {实参1, 实参2, ...}; (6) 
函数( { 实参1, 实参2, ... } ) (7) 
return { 实参1, 实参2, ... } ;  (8) 
对象[ { 实参1, 实参2, ... } ] (9) 
对象 = { 实参1, 实参2, ... }  (10)    
U( { 实参1, 实参2, ... } )  (11)    
 { T 成员 = { 实参1, 实参2, ... }; };    (12)    

6.4 聚合初始化

  • 定义:聚合初始化对聚合体进行初始化
  • 语法
T 对象 = {实参1, 实参2, ...}; (1) 
T 对象 {实参1, 实参2, ... };  (2) (C++11 )
T 对象 = { .指派符 = 实参1 , .指派符 { 实参2 } ... };   (3) (C++20 )
T 对象 { .指派符 = 实参1 , .指派符 { 实参2 } ... }; (4) (C++20 )
T 对象 (实参1, 实参2, ...);   (5) (C++20 )

6.5 静态初始化和动态初始化

  • 参考
  • 静态初始化,适用于静态存储期的变量,在 main 函数之前就初始化了
  • 动态初始化:在运行时初始化。(静态初始化之外的全部都是动态初始化)

7 移动语义

7.1 类移动语义

  • 条件:
  • 实现移动构造
  • 重载移动赋值运算符
  • 示例
template<class T>
class A
{
public:
    A() = default;
    //1.1拷贝构造函数,有2种方式,同一类中只能存在一种
    A(const A& val) {//方式一
        cout << "拷贝构造: A(const A& val)" << endl;
    }
    //A(const A<T>& val) {//方式二
    //    cout << "拷贝构造: A(const A<T>& val)" << endl;
    //}

    //1.2模板拷贝构造函数
    template<class T1>
    A(const A<T1>& val) {
        cout << "模板拷贝构造: A(const A<T1>& val)" << endl;
    }

    //2.1重载赋值运算符,有4种方式,同一类中只能存在一种
    A& operator =(const A& val) {//方式一
        cout << "重载赋值运算符: A operator =(const A& val)" << endl;
        return *this;
    }
    //A& operator =(const A<T>& val) {//方式二
    //    cout << "重载赋值运算符: A& operator =(const A<T>& val)" << endl;
    //    return *this;
    //}
    //A<T>& operator =(const A& val) {//方式三
    //    cout << "重载赋值运算符:  A<T>& operator =(const A& val)" << endl;
    //    return *this;
    //}
    //A<T>& operator =(const A<T>& val) {//方式四
    //    cout << "重载赋值运算符:  A<T>& operator =(const A<T>& val)" << endl;
    //    return *this;
    //}

    //2.2模板重载赋值运算符
    template<class T1>
    A<T>& operator =(const A<T1>& val) {
        cout << "模板重载赋值运算符:  A<T>& operator =(const A<T1>& val)" << endl;
        return *this;
    }

    //3.1移动拷贝构造函数,有2种方式,同一类中只能存在一种
    A(A&& val) {//方式一
        cout << "移动拷贝构造: A(A&& val)" << endl;
    }
    //A(A<T>&& val) {//方式二
    //    cout << "移动拷贝构造: A(A<T>&& val)" << endl;
    //}

    //3.2 模板移动拷贝构造函数
    template<class T1>
    A(A<T1>&& val) {
        cout << "模板移动拷贝构造: A(A<T1>&& val)" << endl;
    }

    //4.1移动赋值运算符,有4种方式,同一类中只能存在一种
    A& operator =(A&& val) {
        cout << "移动赋值运算符: A& operator =(A&& val)" << endl;
        return *this;
    }
    /*A<T>& operator =(A&& val) {
        cout << "移动赋值运算符: A& operator =(A&& val)" << endl;
        return *this;
    }
    A& operator =(A<T>&& val) {
        cout << "移动赋值运算符: A& operator =(A&& val)" << endl;
        return *this;
    }
    A<T>& operator =(A<T>&& val) {
        cout << "移动赋值运算符: A& operator =(A&& val)" << endl;
        return *this;
    }*/

    //模板移动赋值运算符
    template<class T1>
    A& operator =(A<T1>&& val) {
        cout << "模板移动赋值运算符: A& operator =(A<T1>&& val)" << endl;
        return *this;
    }
private:
    vector<T> mv_value;
};

int main()
{
    A<int> ai1;
    A<int> ai2(ai1);
    ai1 = ai2;
    A<double> ad1(ai1);
    A<double> ad2 = ai1;
    ad2 = ai1;
    A<int> ai3(std::move(ai1));//ai1不再使用   
    A<double> ad3(std::move(ai3));//ai3不再使用
    ai3 = std::move(ai2);//ai2不再使用
    A<int> ai4;
    ad3 = std::move(ai4);//ai4不再使用
    ad3 = std::move(ad1);//ad1不再使用
}
//控制台输出结果:
拷贝构造: A(const A& val)
重载赋值运算符: A operator =(conSst A& val)
模板拷贝构造: A(const A<T1>& val)
模板拷贝构造: A(const A<T1>& val)
模板重载赋值运算符:  A<T>& operator =(const A<T1>& val)
移动拷贝构造: A(A&& val)
模板移动拷贝构造: A(A<T1>&& val)
移动赋值运算符: A& operator =(A&& val)
模板移动赋值运算符: A& operator =(A<T1>&& val)
移动赋值运算符: A& operator =(A&& val)

7.2 左值引用和右值引用调用规则

  • 非模板函数
  • 当仅实现 T& 时,只接受左值
  • 当只实现 T&& 时, 只接受右值
  • 当仅实现 const T& 时,可接受左值、常左值和右值
  • 当同时实现 T&T&&const T& 时,T& 优先接受左值,const T& 优先接受常左值,T&& 优先接受右值
  • 模板函数
  • 当仅实现 T& 时,只接受左值
  • 当只实现 T&& 时, 可接受左值、常左值和右值
  • 当仅实现 const T& 时,可接受左值、常左值和右值
  • 当同时实现 T&T&&const T& 时,T& 优先接受左值,const T& 优先接受常左值,T&& 接受右值
  • 示例
#if 0
template<class T>
void Test(const T&) {
    cout << "模板: const T&" << endl;
}
template<class T>
void Test(T&){
    cout << "模板: T&" << endl;
}
template<class T>
void Test(T&&) {
    cout << "模板: T&&" << endl;
}
#endif
#if 1
void Test(int&&){
    cout << "int&&" << endl;
}
void Test(const int&) {
    cout << "const int&" << endl;
}
void Test(int&) {
    cout << "int&" << endl;
}
#endif
int main()
{
    const int t1 = 1;
    int t2 = 2;
    Test(t1);
    Test(t2);
    Test(2);
    Test(std::move(t2));
}

8 可变参数

8.1 可变参数普通函数

  • 变参函数声明形式:return_type function_name(parameter_type format, ...);return_type function_name(parameter_type format ...);, 逗号可省略。
  • 变参函数列表不能是引用类型
  • 变参函数参数列表中的类型并不代表所有参数类型,只限制第一个参数类型。
  • 变参函数参数列表中给出的类型可以是任意类型,但必须能够提供足够信息让展开参数包时知道参数类型以及参数个数。
#include <stdio.h>
#include <stdarg.h>
 //这个num作用是提供出参数个数的作用,但无法提供参数类型作用
double average(int num,...)
{

    va_list valist;
    double sum = 0.0;
    int i;

    /* 为 num 个参数初始化 valist */
    va_start(valist, num);

    /* 访问所有赋给 valist 的参数 下一个参数类型是int型 */
    for (i = 0; i < num; i++)
    {
       sum += va_arg(valist, int);
    }
    /* 结束并清理为 valist 保留的内存 */
    va_end(valist);

    return sum/num;
}
//这个fmt能提供参数个数还能提供参数类型(fmt具有一定规则)
void simple_printf(const char* fmt...) // C 风格 "const char* fmt, ..." 亦合法
{
    va_list args;
    va_start(args, fmt);

    while (*fmt != '\0') {
        if (*fmt == 'd') {
            int i = va_arg(args, int);
            std::cout << i << '\n';
        } else if (*fmt == 'c') {
            // 注意自动转换到整数类型
            int c = va_arg(args, int);
            std::cout << static_cast<char>(c) << '\n';
        } else if (*fmt == 'f') {
            double d = va_arg(args, double);
            std::cout << d << '\n';
        }
        ++fmt;
    }

    va_end(args);
}

int main()
{
   printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
   printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
   simple_printf("dcff", 3, 'a', 1.999, 42.5); 
}

8.2 可变参数模板函数

8.2.1 可变模板函数

  • 下面是一个最简单的变参模板函数,利用 sizeof... 运算符返回参数个数。
template<typename ...T>
size_t count(T...args)
{
    return sizeof...(args);
}
int main()
{
  auto num=count(1,"2",false);//num==3
}

8.2.2 递归展开参数包

  • 递归展开参数包的重点是提供一个递归终止函数。
#include<iostream>
using namespace std;

template <typename T>
void fun(const T& t) {
    const std::type_info& tpif = typeid(T);
    cout << "value:" << t << "的类型是" << tpif.name() << '\n';
}
template <typename T, typename ... Args>
void fun(const T& t, Args ... args) {
    const std::type_info& tpif = typeid(T);
    cout << "value:" << t << "的类型是" << tpif.name() << '\n';
    fun(args...);//递归解决,利用模板推导机制,每次取出第一个,缩短参数包的大小。
}
int main() {
    fun("1", false, 2);
}
//输出结果
//value:1的类型是char [2]
//value:0的类型是bool
//value:2的类型是int
  • 递归终止函数参数个数没有限制,可以无参,也可以多参。

注意:建议参数越少越好,当设计 0 参时,设计的变参函数才可以匹配 0 参输入,终止函数参数为 n 个,代表你函数输入的参数最少为 n 个

//0参实现递归终止函数
void fun() {

}
//多参实现递归终止函数
template <typename T1,typename T2>
void fun(const T1& t1,const T2& t2) {
    const std::type_info& tpif1 = typeid(T1);
    const std::type_info& tpif2 = typeid(T2);
    cout << "value:" << t1 << "的类型是" << tpif1.name() << '\n';
    cout << "value:" << t2 << "的类型是" << tpif2.name() << '\n';
}

8.2.3 逗号表达式展开参数包

#include<iostream>
using namespace std;
void print(T t)
{
    cout << t << endl;
}

template <class ...Args>
void expand(Args... args)
{
    bool temp[] = { (print(args), true)... };
}
int main()
{
  expand(1,2,"1");
}

8.2.4 折叠表达式展开参数包

  • c++17 折叠表达式比逗号表达式方式更好
#include<iostream>
#include<type_traits>
using namespace std;
//折叠表达式 
template<typename ...T>
requires (is_arithmetic_v<T>&&...)
auto sum(T...args)->decltype((... + args))
{
    return (... + args);
}

//不建议逗号表达式
void sum_(double& all, const double val2)
{
    all += val2;
}
//c++14 开始auto推导返回类型不再需要尾随声明
template<typename T>
concept arithmetic = is_arithmetic_v<T>;
template<arithmetic...T>
auto sum1(T...args)
{
    double val = 0.0;
    bool temp[] = { (sum_(val, args), true)...};
    return val;
}
int main() {
    auto val = sum(1, 2.2); val被推导为double
  auto val1 = sum1(1, 2.3);
}

8.3 可变参数模板类

8.3.1 递归展开参数包

  • 递归展开一般需要 3 个类,一个前向声明,一个递归终止,一个基本定义
//前向声明
template<typename... Args>
struct Sum;

//基本定义
template<typename First, typename... Rest>
struct Sum<First, Rest...>
{
    enum { value = Sum<First>::value + Sum<Rest...>::value };
};

//递归终止
template<typename Last>
struct Sum<Last>
{
    enum { value = sizeof (Last) };
};
int main()
{
    auto sz=Sum<int, float>::value;//sz==8
}

9 存储持续性

9.1 自动存储持续性

  • 函数类定义的 非静态局部变量,出了作用域自动释放。

9.2 静态存储持续性

  • 在函数外定义的 全局变量
  • static 修饰的变量

9.3 线程存储持续性

  • thread_local 修饰的变量
title: 为什么thread_local变量不能是非静态类成员?

`thread_local` 变量必须是静态或全局范围的,因为每个线程需要一个独立的存储位置,而非静态成员变量是与类的每个对象实例相关联的,每个对象实例不一定在不同的线程中。这会导致问题,因为 `thread_local` 变量无法确定它应该与哪个对象实例相关联。

9.4 动态存储持续性

  • new 声明的变量

10 链接性

  • 示例
static int sta_i;//静态存储期,内部链接性
int ext_i;//静态存储期,外部链接性
void fun()
{
    static int l_a;//静态存储期,无链接性
    int l_b;
}
int main()
{
}

10.1 外部链接性

  • 特点:可被所有文件访问
  • 示例:
  • 全局变量
  • 全局函数

10.2 内部链接性

  • 特点:只能在本文件被访问
  • 示例:
  • static 修饰的变量、函数

10.3 无链接性

  • 特点: 只在本函数作用域被访问
  • 示例:
  • static 修饰的局部变量

10.4 函数与链接性

  • 外部链接性函数:函数默认链接性是外部的,可以在函数声明处使用 extern 修饰(可选),指明函数定义在其它地方,就如同 extern 修饰的变量一样。
  • 内部链接性函数:在函数的声明和定义都使用 static 修饰,表明函数只能在当前文件被使用,不能被其它文件链接。

11 动态库和静态库

3 种库加载方式

  • 动态库静态加载
  • 动态库动态加载
  • 静态库加载
title: linux和windows下静态库动态库后缀

- windows:

  - 静态库:xxx.lib

  - 动态库:xxx.dll

- Linux:Linux上库命名格式,以lib起始,xxx表示库名,.a结尾是静态库,.so结尾是动态库

  - 静态库:libxxx.a(.a代表achieve)

  - 动态库:libxxx.so(.so代表share object)
title: windows下生成dll时会附带lib

在生成动态库时也会生成一个lib文件,但这个lib文件与静态库lib文件内容是不一样的。此时的lib文件称导*动态库入库*,主要含动动态库接口入口等信息,而函数主要实现还是在动态库dll文件中。而静态库lib类似于将可执行程序打包,里面是含有完完整整的实现。

11.1 动态库静态加载

特点:使用 lib 和 dll,不使用库函数调用 dll 来导入接口实现。

11.1.1 第一步:生成 dll、lib

  1. 新建 vs 项目
  2. 添加.h .cpp 文件
  3. 设置 vs 项目属性

1. 更改配置属性为动态态库,选择,项目 ->属性 ->常规 ->配置类型 ->动态库 (dll)

2. 添加编译宏,选择,“项目 ->属性 ->C/C++ ->预处理器 ->预处理器定义”中添加 APIEXPORT 宏

  1. 生成解决方案

  2. add.h 文件内容

#pragma once



#ifdef APIEXPORT

#define EXTERN_API extern "C" __declspec(dllexport)

#else

#define EXTERN_API extern "C" __declspec(dllimport)

#endif



EXTERN_API int add(int elm1, int elm2);
  • add.cpp 文件内容
#include "add.h"



int add(int elm1, int elm2)

{

    return elm1+ elm2;

}

11.1.2 第二步:加载 dll、lib

  1. 新建 vs 项目
  2. 添加主程序入口函数.cpp 文件 (包含 main 函数的源文件)
  3. 添加第一步生成 dll 中的.h 文件.dll 文件.lib 文件

1. .h .lib 可随意放置,使用时,指定正确的路径即可

2. 放置.dll 文件在和.exe 文件同一目录下

  • main.cpp 文件内容
//动态库静态加载

#include <iostream>

#include "add.h"//包含头文件

#pragma comment(lib,"../Debug/libMathFunctions.lib")//指定.lib文件的位置

int main()

{

    std::cout<<add(1, 2);

    return 0;

}

11.2 动态库动态加载

11.2.1 第一步:生成 dll、lib

此步骤参考 动态库静态加载第一步:生成dll、lib

11.2.2 第二步:加载 dll

  1. 新建 vs 项目
  2. 添加主程序入口函数.cpp 文件 (包含 main 函数的源文件)

1. 使用 Windows 库函数加载 dll

2. LoadLibraryA 加载 dll

3. GetProcAddress 获取函数地址

4. FreeLibrary 释放句柄

  • main.cpp 文件内容
//动态库动态加载

#include <iostream>

#include <Windows.h>

int main()

{

    HINSTANCE h = LoadLibraryA("libMathFunctions.dll");//第一步

    typedef int(*FunPtr)(int a, int b);//定义函数指针

    if (h == NULL)

    {

        FreeLibrary(h);

        printf("load lib error\n");

    }

    else

    {

        FunPtr funPtr = (FunPtr)GetProcAddress(h, "add");//第二步

        if (funPtr != NULL)

        {

            int result = funPtr(8, 3);

            printf("8 + 3 =%d\n", result);

        }

        else

        {

            printf("get process error\n");

            printf("%d", GetLastError());

        }

        FreeLibrary(h);//第三步

    }

    return 0;

}

11.3 静态库加载

11.3.1 第一步:生成 lib

  1. 新建 vs 项目
  2. 添加.h .cpp 文件
  3. 设置 vs 项目属性

1. 更改配置属性为动态态库,选择,项目 ->属性 ->常规 ->配置类型 ->静态库 (lib)

  1. 生成解决方案

  2. add.h 文件

#pragma once

int add(int elm1, int elm2);
  • add.cpp 文件
#include "add.h"

int add(int elm1, int elm2)

{

    return elm1 + elm2;

}

11.3.2 第二步:加载 lib

  1. 新建 vs 项目
  2. 添加主程序入口函数.cpp 文件 (包含 main 函数的源文件)
  3. 添加第一步生成 lib 中的.lib 文件.h 文件

1. .lib .h 文件可随意放置,使用时,指定正确的路径即可

main.cpp 文件

//静态库加载

#include <iostream>

#include "math/add.h"

#pragma comment(lib,"../Debug/libMathFunctions.lib")

int main()

{

    std::cout << add(1, 2);

    return 0;

}

11.3.3 注意事项

  1. 动态库需要使用 __declspec(dllexport) 导出和 __declspec(dllimport) 加载
  2. 静态库不需要 __declspec(dllexport) 和 __declspec(dllimport)

12 别名

12.1 typedef typename #define namespace using 区别

12.2 关键字 typedef 定义别名

//例1:定义变量别名
typedef unsigned int  size_t; //size_t是unsigned int的别名
size_t i=2; //等价于 unsigned int i=2;
//例2:定义结构别名
typedef struct SA
{

}Sa,*Spa; //C结构写法,SA结构的一个指针对象别名Spa,和一个对象别名Sa
Sa s;
Spa* sp;

12.3 typename

用于模板声明

template<typename T>
class S{};

用于模板类嵌套从属命名时,强调这是一个类型

//例1:
template<typename C>
void f(const C& container, typename C::iterator iter);
//例2:
template<typename C>
class S
{
    typename C::iterator iter1;//声明一个 C::iterator 的成员 iter1
    using iter2=typename C::iterator; 定义别名 iter2 类型是C::iterator
};

12.4 文本替换宏 define

#define PI 3.1415926 //用PI代替3.1415926
double db = PI; //db=3.1415926

12.5 using

用于命名空间

//例1:
using namespace std;//命名空间std内的成员可以直接使用
cout <<"Hello World!";//true
int i;
cin >> i;//true
//例2:
using namespace std::cout;//可以直接使用命名空间std内的cout成员
cout <<"Hello World!";//true
int i;
cin >> i;//error
std::cin >> i;//true

用于类成员 1. 用于将 基类成员引入到派生类 的定义中,例如将基类的受保护成员暴露为派生类的公开成员 2. 用于 继承构造函数

struct B {
    B(){ std::cout << "B构造\n"; }
protected:
    void g(char) { std::cout << "B::g\n"; }
    int m; // B::m 为受保护
};

struct D : B {
    using B::m; // D::m 为公开
    using B::g; // D::g(char)变成公开
    void g(int) { std::cout << "D::g\n"; } // g(int) 与 g(char) 均作为 D 成员可见
    using B::B;//继承B的构造
};
int main(int argc, char* argv[])
{

    D d;// print: B构造 【d调用基类B的构造】
    d.m; //【类外可访问,m从基类的保护变公有】
    d.g(1); //print: D::g 
    d.g('c');//print: B::g
}
  1. 用于类型别名与别名模板声明
    template<typename T>
    using myVec=vector<T>;
    myVec<int> vec;

12.6 namespace 使用在命名空间上

语法: * namespace 别名 = 命名空间名; (1) * namespace 别名 = :: 命名空间名; (2) * namespace 别名 = 嵌套名:: 命名空间名; (3)

namespace Q {
    namespace V { // V 是 Q 的成员,且完全在 Q 内定义
  // namespace Q::V { // C++17 中对上述二行的替代写法
        class C { void m(); }; // C 是 V 的成员且完全定义于 V 内
                               // C::m 仅声明
        void f(); // f 是 V 的成员,但只在此声明
    }
    void V::f() // V 的成员 f 的 V 外定义
                // f 的外围命名空间仍是全局命名空间、Q 与 Q::V
    {
        extern void h(); // 这声明 ::Q::V::h
    }
    void V::C::m() // V::C::m 的命名空间(及类)外定义
                   // 外围命名空间是全局命名空间、Q 与 Q::V
    {
    }
}

int main(int argc, char* argv[])
{
    namespace nm1 = ::Q::V;
    namespace nm2 = Q::V;
    namespace nm3 = Q;
}