04-📝C++核心语法|面向对象2【友元、内部类与局部类、强化训练(数组类封装)、运算符重载、仿函数、模板、类型转换、 C++标准、错误&&异常、智能指针】

简介: 复习`C++核心语法`,且适当进行汇编探索底层实现原理,进一步夯实基础,为以后的`底层开发`、`音视频开发`、`跨平台开发`、`算法`等方向的进一步学习埋下伏笔。

一、前言

最近刚好有空,趁这段时间,复习一下C++语言,进一步夯实基础,为以后的底层开发音视频开发跨平台开发算法等方向的进一步学习埋下伏笔

我们在上一篇文章中,已经充分说明,C++语言是对C的扩展,建立在对C语言知识掌握的基础上学习C++是事半功倍的\
如果你对C语言已经淡忘,或者没有学过C语言,且一时半会没有思路如何筛选可靠的C语言学习资料,可以借鉴我的这几篇文章:

1. C语言核心知识

二、友元、内部类与局部类

1. 友元

  • 类的主要特点之一是 数据隐藏,即类的私有成员无法在类的外部(作用域之外)访问
  • 但是,有时候 需要在类的外部访问类的私有成员,怎么办?
  • 解决方法是使用友元函数,友元函数是一种特权函数,C++允许这个特权函数访问私有成员
  • 这一点从现实生活中也可以很好的理解:
    • 比如你的家,有客厅,有你的卧室,那么你的客厅是Public的,所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去
    • 但是呢,你也可以允许你的闺蜜好基友进去。
    • 程序员可以把一个 全局函数、某个类中的成员函数、甚至整个类 声明为友元

      1.1 友元语法

  • friend关键字只出现在声明处
  • 其他类、类成员函数、全局函数都可声明为友元
  • 友元函数 不是 类的成员,不带this指针
  • 友元函数 可访问对象 任意成员属性,包括私有属性
  • 示例代码:
    ```C++
    class Building;
    //友元类
    class MyFriend {
    public:
       //友元成员函数
       void LookAtBedRoom(Building& building);
       void PlayInBedRoom(Building& building);
    
    };

class Building {
//全局函数做友元函数
friend void CleanBedRoom(Building& building);

 #if 0
     //成员函数做友元函数
     friend void MyFriend::LookAtBedRoom(Building& building);
     friend void MyFriend::PlayInBedRoom(Building& building);
 #else   
     //友元类
     friend class MyFriend;
 #endif

 public:
     Building();
 public:
     stringmSittingRoom;
 private:
     stringmBedroom;

};

void MyFriend::LookAtBedRoom(Building& building){
cout<< "我的朋友参观" << building.mBedroom << endl;
}

void MyFriend::PlayInBedRoom(Building& building){
cout<< "我的朋友玩耍在" << building.mBedroom << endl;
}

//友元全局函数
void CleanBedRoom(Building& building){
cout<< "友元全局函数访问" << building.mBedroom << endl;
}

Building::Building(){
this->mSittingRoom = "客厅";
this->mBedroom = "卧室";
}

int main(){
Buildingbuilding;
MyFriendmyfriend;
CleanBedRoom(building);
myfriend.LookAtBedRoom(building);
myfriend.PlayInBedRoom(building);
system("pause");

 return EXIT_SUCCESS;

}

- 友元类注意
    - 1. 友元关系不能被继承。
    - 2. 友元关系是单向的,类A是类B的朋友,但类B不一定是类A的朋友。
    - 3. 友元关系不具有传递性。类B是类A的朋友,类C是类B的朋友,但类C不一定是类A的朋友
- 思考: C++是纯面向对象的吗?
    - 如果一个类被声明为friend,意味着它不是这个类的成员函数,却可以修改这个类的私有成员,而且必须列在类的定义中,因此他是一个特权函数。
    - C++不是完全的面向对象语言,而只是一个混合产品。
    - 增加friend关键字只是用来解决一些实际问题,这也说明这种语言是不纯的。
    - 毕竟C++设计的目的是为了实用性,而不是追求理想的抽象。
### 1.2 友元|总结
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/db23d03cbd2b46339956dd46960991c0~tplv-k3u1fbpfcp-zoom-1.image)
- 友元包括 `友元函数` 和 `友元类`
- 如果将函数A(非成员函数)声明为类C的友元函数,那么函数A就能直接访问类C对象的所有成员
- 如果将类A声明为类C的友元类,那么类A的所有成员函数都能直接访问类C对象的所有成员
- 友元破坏了面向对象的封装性,但在某些频繁访问成员变量的地方可以提高性能
## 2. 内部类与局部类
### 2.1 内部类
- 如果将类A定义在类C的内部,那么类A就是一个内部类(嵌套类)
- **内部类的特点**
    - 支持public、protected、private权限
    - 成员函数可以直接访问其外部类对象的所有成员(反过来则不行) 
    - 成员函数可以直接不带类名、对象名访问其外部类的static成员
    - 不会影响外部类的内存布局
    - 可以在外部类内部声明,在外部类外面进行定义
- **声明和实现分离**
    ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/24389b71901d4a26b79d7fdad23ca42e~tplv-k3u1fbpfcp-zoom-1.image)
### 2.2 局部类
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e3b7e889a467471ebdbd7e5b5a9bf931~tplv-k3u1fbpfcp-zoom-1.image)
- **在一个函数内部定义的类,称为局部类**
- **局部类的特点**
    - 作用域仅限于所在的函数内部
    - 其所有的成员必须定义在类内部,不允许定义static成员变量
    - 成员函数不能直接访问函数的局部变量(static变量除外)
## 3. 强化训练(数组类封装)
- MyArray.h
    ```C++
    #ifndef MYARRAY_H
    #define MYARRAY_H
    class MyArray{
        public:
            //无参构造函数,用户没有指定容量,则初始化为100
             MyArray();
            //有参构造函数,用户指定容量初始化
            explicit MyArray(int capacity);
            //用户操作接口
            //根据位置添加元素
            void SetData(int pos, int val);
            //获得指定位置数据
            int GetData(int pos);
            //尾插法
            void PushBack(int val);
            //获得长度
            int GetLength();
            //析构函数,释放数组空间
            ~MyArray();
        private:
            int mCapacity; //数组一共可容纳多少个元素
            int mSize; //当前有多少个元素
            int* pAdress; //指向存储数据的空间
    };
    #endif
  • MyArray.cpp

       #include"MyArray.h"
    
       MyArray::MyArray(){
           this->mCapacity = 100;
           this->mSize = 0;
           //在堆开辟空间
           this->pAdress = new int[this->mCapacity];
       }
    
       //有参构造函数,用户指定容量初始化
       MyArray::MyArray(int capacity){
           this->mCapacity = capacity;
           this->mSize = 0;
           //在堆开辟空间
           this->pAdress = new int[capacity];
       }
    
       //根据位置添加元素
       void MyArray::SetData(int pos, int val){
           if (pos < 0 || pos > mCapacity - 1){
               return;
           }
           pAdress[pos] = val;
       }
    
       //获得指定位置数据
       int MyArray::GetData(int pos){
           return pAdress[pos];
       }
       //尾插法
       void MyArray::PushBack(int val){
           if (mSize >= mCapacity){
               return;
           }
           this->pAdress[mSize] = val;
           this->mSize++;
       }
       //获得长度
       int MyArray::GetLength(){
           return this->mSize;
       }
    
       //析构函数,释放数组空间
       MyArray::~MyArray(){
           if (this->pAdress != nullptr){
               delete[] this->pAdress;
           }
       }
    
  • TestMyArray.cpp

      #include"MyArray.h"
    
      void test(){
          //创建数组
          MyArraymyarray(50);
    
          //数组中插入元素
          for (int i = 0; i < 50; i++){
              //尾插法
              myarray.PushBack(i);
              //myarray.SetData(i, i);
          }
    
          //打印数组中元素
          for (int i = 0; i < myarray.GetLength(); i++){
              cout<< myarray.GetData(i) << " ";
          }
          cout<< endl;
      }
    

    三、运算符重载

    1. 运算符重载基本概念

  • 运算符重载,就是 对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
  • 运算符重载(operator overloading)只是一种”语法上的方便”,也就是它只是另一种函数调用的方式
  • 在C++中,可以定义一个处理类的新运算符
  • 运算符重载(操作符重载):可以为运算符增加一些新的功能
  • 这种定义很像一个普通的函数定义,只是函数的名字由关键字operator及其紧跟的运算符组成
  • 差别仅此而已。它像任何其他函数一样也是一个函数,当编译器遇到适当的模式时,就会调用这个函数
  • 语法:
    • 定义重载的运算符就像定义函数,只是该函数的名字是operator@,这里的@代表了被重载的运算符。函数的参数中参数个数取决于两个因素
      • 运算符是一元(一个参数)的还是二元(两个参数);
      • 运算符被定义为全局函数(对于一元是一个参数,对于二元是两个参数)还是成员函数(对于一元没有参数,对于二元是一个参数-此时该类的对象用作左耳参数)
    • 两个极端
      • 有些人很容易滥用运算符重载。它确实是一个有趣的工具,但是应该注意,它仅仅是一种语法上的方便而已,是另外一种函数调用的方式
      • 从这个角度来看,只有在能使涉及类的代码更易写,尤其是更易读时(请记住,读代码的机会比我们写代码多多了)才有理由重载运算符
      • 如果不是这样,就改用其他更易用,更易读的方式
      • 对于运算符重载,另外一个常见的反应是恐慌:
      • 突然之间,C运算符的含义变得不同寻常了,一切都变了,所有C代码的功能都要改变!
      • 并非如此,对于内置的数据类型的表中的所有运算符是不可能改变的

        2. 运算符重载碰上友元函数

  • 友元函数是一个全局函数,和我们上例写的全局函数类似,只是友元函数可以访问某个类私有数据
  • 案例: 重载左移操作符(<<),使得cout可以输出对象
  • 代码示例:

       class Person{
           friend ostream& operator<<(ostream& os, Person& person);
           public:
               Person(int id,int age){
                   mID= id;
                   mAge= age;
           }
           private:
               int mID;
               int mAge;
       };
    
       ostream& operator<<(ostream& os, Person& person){
           os<< "ID:" << person.mID << " Age:" << person.mAge;
           return os;
       }
    
       int main(){
           Personperson(1001, 30);
           //cout << person;//cout.operator+(person)
           cout<< person << " | " << endl;
           return EXIT_SUCCESS;
       }
    

3. 可重载的运算符

  • 几乎C中所有的运算符都可以重载,但运算符重载的使用是相当受限制的
  • 特别是不能使用C中当前没有意义的运算符(例如用求幂)
  • 不能改变运算符优先级
  • 不能改变运算符的参数个数。这样的限制有意义
  • 否则,所有这些行为产生的运算符只会混淆而不是澄清寓语意

    4. 自增自减(++/--)运算符重载

  • 重载的++和--运算符有点让人不知所措,因为我们总是希望能根据它们出现在所作用对象的前面还是后面来调用不同的函数。
  • 解决办法很简单,例如当编译器看到++a(前置++),它就调用operator++(a),当编译器看到a++(后置++),它就会去调用operator++(a,int)
  • 代码示例:

      class Complex{
           friend ostream& operator<<(ostream& os,Complex& complex){
               os<< "A:" << complex.mA << " B:" << complex.mB << endl;
               return os;
           }
    
           public:
               Complex(){
                   mA= 0;
                   mB= 0;
               }
    
           //重载前置++
           Complex&operator++(){
               mA++;
               mB++;
               return *this;
           }
    
           //重载后置++
           Complexoperator++(int){ 
               Complextemp;
               temp.mA = this->mA;
               temp.mB = this->mB;
               mA++;
               mB++;
               return temp;
           }
    
           //前置--
           Complex& operator--(){
               mA--;
               mB--;
               return *this;
           }
    
           //后置--
           Complexoperator--(int){
               Complextemp;
               temp.mA = mA;
               temp.mB = mB;
               mA--;
               mB--;
               return temp;
           }
    
           void ShowComplex(){
               cout<< "A:" << mA << " B:" << mB << endl;
           }
    
           private:
               int mA;
               int mB;
       };
    
       void test(){
           Complexcomplex;
           complex++;
           cout<< complex;
           ++complex;
           cout<< complex;
           Complexret = complex++;
           cout<< ret;
           cout<< complex;
           cout<< "------" << endl;
           ret--;
           --ret;
           cout<< "ret:" << ret;
           complex--;
           --complex;
           cout<< "complex:" << complex;
       }
    
  • 优先使用++和--的标准形式,优先调用前置++
  • 如果定义了++C,也要定义C++,递增操作符比较麻烦,因为他们都有 前缀后缀 形式,而两种语义略有不同。重载operator++和operator--时应该模仿他们对应的内置操作符
  • 对于++和--而言,后置形式是先返回,然后对象++或者--,返回的是对象的原值。前置形式,对象先++或--,返回当前对象,返回的是新对象。其标准形式为:
  • 调用代码时候,要优先使用前缀形式,除非确实需要后缀形式返回的原值,前缀和后缀形式语义上是等价的,输入工作量也相当,只是效率经常会略高一些,由于前缀形式少创建了一个临时对象

5. 指针运算符(*、->)重载

  • 代码示例:
    ```C++
    class Person{
    public:
      Person(int param){
          this->mParam = param;
      }
      void PrintPerson(){
          cout<< "Param:" << mParam << endl;
      }
    
    private:
      int mParam;
    
    };

class SmartPointer{
public:
SmartPointer(Person* person){
this->pPerson = person;
}

    //重载指针的->、操作符
    Person operator->(){
        return pPerson;
    }

    Person& operator*(){
        return pPerson;
    }

    ~SmartPointer(){
        if (pPerson != NULL){
            delete pPerson;
        }
    }

public:
    Person pPerson;

};

void test01(){
//Person* person = newPerson(100);
//如果忘记释放,那么就会造成内存泄漏
SmartPointerpointer(new Person(100));
pointer->PrintPerson();
}


## 6. 赋值(=)运算符重载
- 赋值符常常初学者的混淆。这是毫无疑问的,因为’=’在编程中是最基本的运算符,可以进行赋值操作,也能引起拷贝构造函数的调用
- 代码示例
```C++
class Person{
    friend ostream& operator<<(ostream& os,const Person& person){
        os<< "ID:" << person.mID << " Age:" << person.mAge << endl;
        return os;
    }
public:
    Person(int id,int age){
        this->mID = id;
        this->mAge = age;
    }
    //重载赋值运算符
    Person& operator=(const Person& person){
        this->mID = person.mID;
        this->mAge = person.mAge;
        return this;
    }
private:
    int mID;
    int mAge;
};

//1. =号混淆的地方
void test01(){
    Personperson1(10, 20);
    Personperson2 = person1; //调用拷贝构造
    //如果一个对象还没有被创建,则必须初始化,也就是调用构造函数
    //上述例子由于person2还没有初始化,所以会调用构造函数
    //由于person2是从已有的person1来创建的,所以只有一个选择
    //就是调用拷贝构造函数
    person2= person1; //调用operator=函数
    //由于person2已经创建,不需要再调用构造函数,这时候调用的是重载的赋值运算符
}

//2. 赋值重载案例
void test02(){
    Personperson1(20, 20);
    Personperson2(30, 30);
    cout<< "person1:" << person1;
    cout<< "person2:" << person2;
    person2= person1;
    cout<< "person2:" << person2;
}
//常见错误,当准备给两个相同对象赋值时,应该首先检查一下这个对象是否对自身赋值了
//对于本例来讲,无论如何执行这些赋值运算都是无害的,但如果对类的实现进行修改,那么将会出现差异;
//3. 类中指针
class Person2{
    friend ostream& operator<<(ostream& os, const Person2& person){
        os << "Name:" << person.pName << " ID:" << person.mID << " Age:" << person.mAge << endl;
        return os;
    }
public:
    Person2(char* name,int id, int age){
        this->pName = new char[strlen(name) + 1];
        strcpy(this->pName, name);
        this->mID = id;
        this->mAge = age;
    }
    #if 1
        //重载赋值运算符
        Person2& operator=(const Person2& person){
            //注意:由于当前对象已经创建完毕,那么就有可能pName指向堆内存
            //这个时候如果直接赋值,会导致内存没有及时释放
            if (this->pName != NULL){
                delete[] this->pName;
            }
            this->pName = new char[strlen(person.pName) + 1];
            strcpy(this->pName,person.pName);
            this->mID = person.mID;
            this->mAge = person.mAge;
            return this;
        }
    #endif

    //析构函数
    ~Person2(){
        if (this->pName != NULL){
            delete[] this->pName;
        }
    }
    private:
        char* pName;
        int mID;
        int mAge;
};

void test03(){
    Person2 person1("John",20,30);
    Person2 person2("Edward",30,30);
    cout << "person1" << person1;
    cout << "person2" << person2;
    person2 = person1;
    cout << "person2" << person2;
}
  • 如果没有重载赋值运算符,编译器会自动创建默认的赋值运算符重载函数。
  • 行为类似默认拷贝构造,进行简单值拷贝。

    7. 等于和不等于(==、!=)运算符重载

  • 代码示例:
    ```C++
    class Complex{
    public:

      Complex(*char name*,int id,int age){
          this->pName = new char[strlen(name) + 1];
               strcpy(this->pName, name);
          this->mID = id;
          this->mAge = age;
      }
    

    //重载==号操作符
    bool operator==(const Complex& complex){

      if (strcmp(this->pName,complex.pName) == 0 && 
          this->mID == complex.mID && 
          this->mAge == complex.mAge){
          return true;
      }
      return false;
    

    }
    //重载!=操作符
    bool operator!=(const Complex& complex){

      if (strcmp(this->pName, complex.pName) != 0 || 
      this->mID != complex.mID || 
      this->mAge != complex.mAge){
          return true;
      }
      return false;
    

    }

    ~Complex(){

      if (this->pName != NULL){
          delete[] this->pName;
      }
    

    }

    private:

      char pName;
      int mID;
      int mAge;
    

    };

void test(){
Complexcomplex1("aaa", 10, 20);
Complexcomplex2("bbb", 10, 20);
if (complex1 == complex2){ cout << "相等!" << endl; }
if (complex1 != complex2){ cout << "不相等!" << endl; }
}

## 8. 函数调用符号()重载
- 代码示例:
    ```C++
    class Complex{
        public:
        int Add(int x,int y){
            return x + y;
        }
        int operator()(int x,int y){
            return x + y;
        }
    };

    void test01(){
        Complexcomplex;
        cout<< complex.Add(10,20) << endl;
        //对象当做函数来调用
        cout<< complex(10, 20) << endl;
    }

9. 不要重载&&、||

  • 不能重载operator&& 和 operator|| 的原因是:
    • 无法在这两种情况下实现内置操作符的完整语义
    • 说得更具体一些,内置版本版本特殊之处在于:
      • 内置版本的&&和||首先计算左边的表达式,如果这完全能够决定结果,就无需计算右边的表达式了--而且能够保证不需要
      • 我们都已经习惯这种方便的特性了。
    • 我们说 操作符重载其实是另一种形式的函数调用而已
    • 对于函数调用总是在函数执行之前对所有参数进行求值
  • 代码示例:
    ```C++
    class Complex{
    public:
      Complex(int flag){
          this->flag = flag;
      }
      Complex& operator+=(Complex& complex){
          this->flag = this->flag + complex.flag;
          return *this;
      }
      bool operator&&(Complex& complex){
          return this->flag && complex.flag;
      }
    
    public:
      int flag;
    
    };

int main(){
Complexcomplex1(0); //flag 0
Complexcomplex2(1); //flag 1
//原来情况,应该从左往右运算,左边为假,则退出运算,结果为假
//这边却是,先运算(complex1+complex2),导致,complex1的flag变为complex1+complex2的值, complex1.a = 1
// 1 && 1
//complex1.operator&&(complex1.operator+=(complex2))
if (complex1 && (complex1 += complex2)){
cout<< "真!" << endl;
} else {
cout<< "假!" << endl;
}
return EXIT_SUCCESS;
}

- 根据内置&&的执行顺序,我们发现这个案例中执行顺序并不是从左向右,而是先右猴左,这就是不满足我们习惯的特性了。
- 由于complex1 += complex2先执行,导致complex1 本身发生了变化,初始值是0,现在经过+=运算变成1,1 && 1输出了真

## 10. 调用父类的运算符重载函数
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4ce75827dd61412293ba3dcde762aef3~tplv-k3u1fbpfcp-zoom-1.image)
## 11. 符号重载总结
- =, [], () 和 -> 操作符只能通过`成员函数`进行重载
- <<  和  >> 只能通过`全局函数`配合`友元函数`进行重载
- 不要重载 && 和 || 操作符,因为无法实现短路规则
- 常规建议
    ![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/78f15df972a244b7b33f4d32851d3acd~tplv-k3u1fbpfcp-watermark.image?)
- 注意
    - 有些运算符不可以被重载,比如
        - 对象成员访问运算符:.
        - 域运算符:::
        - 三目运算符:?:
        - sizeof
    - 有些运算符只能重载为成员函数,比如
        - 赋值运算符:=
        - 下标运算符:[ ]
        - 函数运算符:( )
        - 指针访问成员:→
## 12. 强化训练_字符串类封装
- **MyString.h**
    ```C++
    #define _CRT_SECURE_NO_WARNINGS
    #pragma once
    #include <iostream>
    using namespace std;

    class MyString {
        friend ostream&operator<< (*ostream*  & out, MyString& str);
        friend *istream*&operator>>(istream& in, MyString& str);
    public:
        MyString(const char *);
        MyString(const MyString&);
         ~MyString();
        char&operator[](int index); //[]重载
        //=号重载
        MyString& operator=(const char * str);
        MyString& operator=(const MyString& str); 
        //字符串拼接 重载+号
        MyString operator+(const char * str );
        MyString operator+(const MyString& str);
        //字符串比较
        booloperator== (const char * str);
        booloperator== (const MyString& str);
        private:
            char * pString; //指向堆区空间
            int m_Size; //字符串长度 不算'\0'
    };
  • MyString.cpp\



  • TestMyString.cpp\

13. 附录:运算符和结合性

image.png
image.png



-

四、仿函数(函数对象)

  • 仿函数:将一个对象当作一个函数一样来使用
  • 对比普通函数,它作为对象可以保存状态

    五、智能指针(Smart Pointer)

    1. 传统指针与智能指针

  • 传统指针存在的问题
    • 需要手动管理内存
    • 容易发生内存泄露(忘记释放、出现异常等)
    • 释放之后产生野指针
  • 智能指针就是为了解决传统指针存在的问题
    • auto_ptr:属于C++98标准,在C++11中已经不推荐使用(有缺陷,比如不能用于数组)
    • shared_ptr:属于C++11标准
    • unique_ptr:属于C++11标准

      2. 智能指针的简单自实现

      3. shared_ptr

  • shared_ptr的设计理念
    • 多个shared_ptr可以指向同一个对象,当最后一个shared_ptr在作用域范围内结束时,对象才会被自动释放
  • 可以通过一个已存在的智能指针初始化一个新的智能指针
  • 针对数组的用法

    4. shared_ptr的原理

  • 一个shared_ptr会对一个对象产生强引用(strong reference)
  • 每个对象都有个与之对应的强引用计数,记录着当前对象被多少个shared_ptr强引用着 可以通过shared_ptr的use_count函数获得强引用计数
  • 当有一个新的shared_ptr指向对象时,对象的强引用计数就会+1
  • 当有一个shared_ptr销毁时(比如作用域结束),对象的强引用计数就会-1
  • 当一个对象的强引用计数为0时(没有任何shared_ptr指向对象时),对象就会自动销毁(析构)

    5. 思考下面代码有没有问题

    6. shared_ptr的循环引用



    7. weak_ptr


  • weak_ptr会对一个对象产生弱引用
  • weak_ptr可以指向对象解决shared_ptr的循环引用问题

    8. weak_ptr解决循环引用

    9. unique_ptr

  • unique_ptr也会对一个对象产生强引用,它可以确保同一时间只有1个指针指向对象
  • 当unique_ptr销毁时(作用域结束时),其指向的对象也就自动销毁了
  • 可以使用std::move函数转移unique_ptr的所有权

    六、模板(template)

    1. 泛型编程

  • 泛型,是一种将类型参数化以达到代码复用的技术,C++中使用模板来实现泛型
  • 模板的使用格式如下:
    • template
    • typename和class是等价的
  • 模板没有被使用时,是不会被实例化出来的
  • 模板的声明和实现如果分离到.h和.cpp中,会导致链接错误
  • 一般将模板的声明和实现统一放到一个.hpp文件中

    2. 编译链接


    3. 函数模板

    4. 多参数模板

    5. 类模板

    6. 类模板中的友元函数


    七、类型转换

    1. 转换符

  • C语言风格的类型转换符
    • (type)expression
    • type(expression)
  • C++中有4个类型转换符
    • static_cast
    • dynamic_cast
    • reinterpret_cast
    • const_cast
  • 使用格式:xx_cast<type>(expression)

    2. const_cast

  • 一般用于去除const属性,将const转换成非const

    3. dynamic_cast


  • 一般用于多态类型的转换,有运行时安全检测

    4. static_cast

  • 对比 dynamic_cast ,缺乏运行时安全检测
  • 不能交叉转换(不是同一继承体系的,无法转换)
  • 常用于基本数据类型的转换、非const转成const
  • 使用范围较广

    5. reinterpret_cast

  • 属于比较底层的强制转换,没有任何类型检查和格式转换,仅仅是简单的二进制数据拷贝
  • 可以交叉转换
  • 可以将指针和整数互相转换

    八、C++标准的发展

    1. C++11新特性

  • auto\
    • 可以从初始化表达式中推断出变量的类型,大大简化编程工作
    • 属于编译器特性,不影响最终的机器码质量,不影响运行效率
  • decltype
    • 可以获取变量的类型\
  • nullptr
    • 可以解决NULL的二义性问题\
  • Lambda表达式
    • Lambda表达式
      • 有点类似于JavaScript中的闭包、iOS中的Block,本质就是函数
      • 完整结构: [capture list] (params list) mutable exception-> return type { function body }
        • capture list:捕获外部变量列表
        • params list:形参列表,不能使用默认参数,不能省略参数名
        • mutable:用来说用是否可以修改捕获的变量
        • exception:异常设定
        • return type:返回值类型
        • function body:函数体
      • 有时可以省略部分结构
        • [capture list] (params list) -> return type {function body}
        • [capture list] (params list) {function body}
        • [capture list] {function body}
    • 示例\
    • 外部变量捕获\
    • mutable\

      2. C++14

  • 泛型Lambda表达式\
  • 对捕获的变量进行初始化\

    3. C++17

  • 设置C++标准\

  • 可以进行初始化的if、switch语句\

    九、错误&&异常

    1. 错误

  • 编程过程中的常见错误类型
  • 语法错误
  • 逻辑错误
  • 异常
  • ......

    2. 异常


  • 异常是一种在程序运行过程中可能会发生的错误(比如内存不够)
  • 异常没有被处理,会导致程序终止
  • throw异常后,会在当前函数中查找匹配的catch,找不到就 终止当前函数代码,去上一层函数中查找。如果最终都找不 到匹配的catch,整个程序就会终止

    3. 异常的抛出声明

  • 为了增强可读性和方便团队协作,如果函数内部可能会抛出异常,建议函数声明一下异常类型

    4. 自定义异常类型

    5. 拦截所有类型的异常

    6. 标准异常(std)



相关文章
|
8天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
27 4
|
1月前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
29 2
|
1月前
|
编译器 C语言 C++
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
19 1
|
1月前
|
存储 编译器 数据安全/隐私保护
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解2
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解
29 3
|
1月前
|
编译器 C++
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解1
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解
45 3
|
1月前
|
编译器 C++
【C++】深入探索类和对象:初始化列表及其static成员与友元(三)
【C++】深入探索类和对象:初始化列表及其static成员与友元
|
1月前
|
C++
C++入门4——类与对象3-2(构造函数的类型转换和友元详解)
C++入门4——类与对象3-2(构造函数的类型转换和友元详解)
21 0
|
1月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
22 0
|
2月前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
|
3月前
|
C语言
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)