C++語法(二)

繼承

父類中默認(rèn)構(gòu)造,析構(gòu),拷貝構(gòu)造operator=是不會(huì)被子類繼承下去的

  • 繼承方式
    • public:公有繼承
    • private:私有繼承
    • protected:保護(hù)繼承
1.png

如圖可知,繼承方式會(huì)把父類的屬性進(jìn)行權(quán)限降級(jí);public是不變,protected會(huì)把父類除了私有的全部變?yōu)閜rotected,private會(huì)把全部變成private;而且父類私有的不論怎么繼承字類都不能訪問

class 派生類名:繼承方式 基類名{

}
  • 繼承中的對象模型
#include <iostream>
using namespace std;
#include <cstring>
class Base
{
public:
    int m_A;

protected:
    int m_B;

private:
    int m_C;
};
class Son : public Base
{
public:
    int m_D;
};

main()
{
    //父類私有屬性雖然子類訪問不到,但是還是被繼承下去了;只是編譯器給隱藏了
    cout<<sizeof(Son)<<endl;//16
    return 0;
}
  • 繼承中的構(gòu)造和析構(gòu)順序
#include <iostream>
using namespace std;
#include <cstring>
class Base
{
public:
    Base()
    {
        cout << "Base的構(gòu)造函數(shù)" << endl;
    }
    ~Base()
    {
        cout << "Base的析構(gòu)函數(shù)" << endl;
    }
};
class Son : public Base
{
public:
    Son()
    {
        cout << "Son的構(gòu)造函數(shù)" << endl;
    }
    ~Son()
    {
        cout << "Son的析構(gòu)函數(shù)" << endl;
    }
};

main()
{
    Son s;
    //調(diào)用順序:
    // Base的構(gòu)造函數(shù)
    // Son的構(gòu)造函數(shù)
    // Son的析構(gòu)函數(shù)
    // Base的析構(gòu)函數(shù)
    return 0;
}
#include <iostream>
using namespace std;
#include <cstring>
class Base
{
public:
    Base()
    {
        cout << "Base的構(gòu)造函數(shù)" << endl;
    }
    ~Base()
    {
        cout << "Base的析構(gòu)函數(shù)" << endl;
    }
};
class Other
{
public:
    Other()
    {
        cout << "Other的構(gòu)造函數(shù)" << endl;
    }
    ~Other()
    {
        cout << "Other的析構(gòu)函數(shù)" << endl;
    }
};
class Son : public Base
{
public:
    Son()
    {
        cout << "Son的構(gòu)造函數(shù)" << endl;
    }
    ~Son()
    {
        cout << "Son的析構(gòu)函數(shù)" << endl;
    }
    Other other;
};

main()
{
    Son s;
    //調(diào)用順序:
    // Base的構(gòu)造函數(shù)
    // Other的構(gòu)造函數(shù)
    // Son的構(gòu)造函數(shù)
    // Son的析構(gòu)函數(shù)
    // Other的析構(gòu)函數(shù)
    // Base的析構(gòu)函數(shù)
    return 0;
}
#include <iostream>
using namespace std;
#include <cstring>
class Base
{
public:
    Base(int a)
    {
        cout << "Base的構(gòu)造函數(shù)" << endl;
    }
};
class Son : public Base
{
public:
    // Son(int a=100):Base(a)
    Son(int a):Base(a)//利用初始化列表語法,顯示調(diào)用父類中的其他構(gòu)造函數(shù)
    {
        cout << "Son的構(gòu)造函數(shù)" << endl;
    }
};

main()
{
    // Son s;
    Son s(10);
    return 0;
}

繼承中同名成員的處理

#include <iostream>
using namespace std;
#include <cstring>
class Base
{
public:
    Base()
    {
        this->m_A=10;
    }
    void func(){

    }
    void func(int a){

    }

    int m_A;
};
class Son : public Base
{
public:
    Son()
    {
        this->m_A=100;
    }
    void func(){

    }
    int m_A;
};

main()
{
    Son s;
    //同名就近
    cout<<s.m_A<<endl;//100
    //訪問父類的成員
    cout<<s.Base::m_A<<endl;//10
    //同理:同名成員函數(shù)也是如此


    //當(dāng)子類重新定義了父類中的同名成員函數(shù),子類的成員函數(shù)會(huì)
    //隱藏掉父類中的所有重載版本的同名成員,可以利用作用域顯示的指定調(diào)用
    //注意:只是隱藏,不是干掉了
    // s.func(10); //錯(cuò)誤
    s.Base::func(10);//正確
    return 0;
}
  • 繼承中的同名靜態(tài)成員
#include <iostream>
using namespace std;
#include <cstring>
class Base
{
public:
    Base()
    {
    }
    static void func()
    {
    }
    static int m_A;
};
int Base::m_A = 0;
class Son : public Base
{
public:
    Son()
    {
    }
    static void func()
    {
    }
    static int m_A;
};
int Son::m_A = 100;

main()
{
    Son s;
    //1. 通過對象訪問
    cout << s.m_A << endl;       //100
    cout << s.Base::m_A << endl; //0

    //2. 通過類名訪問
    cout << Son::m_A << endl;       //100
    cout << Son::Base::m_A << endl; //0


    //靜態(tài)函數(shù)調(diào)用
    s.func();
    s.Base::func();

    Son::func();
    Son::Base::func();

    // /當(dāng)子類重新定義了父類中的同名成員函數(shù),子類的成員函數(shù)會(huì)
    //隱藏掉父類中的所有重載版本的同名成員,可以利用作用域顯示的指定調(diào)用
    // Son::Base::func(1);//參考成員函數(shù)的處理,一摸一樣
    return 0;
}

多繼承

#include <iostream>
using namespace std;
#include <cstring>
class Base
{
public:
    Base()
    {
    }
};
class Base1
{
public:
    Base1()
    {
    }
};
class Son : public Base, public Base1
{
public:
    Son()
    {
    }
};
main()
{
    Son s;
    //多繼承如果同名也是通過作用域去精確調(diào)用
    return 0;
}

菱形繼承

兩個(gè)派生類繼承同一個(gè)基類而又有某個(gè)類同時(shí)繼承著兩個(gè)派生類,這種繼承被稱為菱形繼承,或者鉆石型繼承

  • 虛繼承(重要)

避免了內(nèi)存浪費(fèi)和定義不明確


2.png
#include <iostream>
using namespace std;
#include <cstring>
class Animal
{
public:
    int m_Age;
};
//Animal稱為虛基類
class Sheep:virtual public Animal{};
class Tuo:virtual public Animal{};

class SheepTuo:public Sheep,public Tuo{};
main()
{
    SheepTuo s;
    s.Sheep::m_Age=10;
    s.Tuo::m_Age=20;
    cout<< s.Sheep::m_Age<<endl;//20
    cout<< s.Tuo::m_Age<<endl;//20
    cout<< s.m_Age<<endl;//20

    //如果不加virtual則,上面三個(gè)分別是10 20和無法識(shí)別
    //加了之后則m_Age只在Animal中一份,其他幾個(gè)是沒有的
    //Sheep/Tuo有個(gè)vbpter(虛基類指針)指向vbtable里面有個(gè)偏移量(兩個(gè)類偏移量也不同)
    //通過偏移量就可以找到內(nèi)存中唯一一份Animal中的m_Age;其實(shí)就是通過地址尋找
    return 0;
}

多態(tài)

c++支持編譯時(shí)多態(tài)(靜態(tài)多態(tài))和運(yùn)行時(shí)多態(tài)(動(dòng)態(tài)多態(tài)),運(yùn)算符重載和函數(shù)重載就是編譯時(shí)多態(tài),而派生類和虛函數(shù)實(shí)現(xiàn)是運(yùn)行時(shí)多態(tài)

靜態(tài)多態(tài)和動(dòng)態(tài)多態(tài)的區(qū)別就是函數(shù)地址是早綁定(靜態(tài)聯(lián)編)還是晚綁定(動(dòng)態(tài)聯(lián)編)。如果函數(shù)的調(diào)用再編譯階段就能確定函數(shù)的調(diào)用地址,并產(chǎn)生代碼就是靜態(tài)多態(tài)。而如果函數(shù)的調(diào)用地址不能編譯不能在編譯期間確定,而需要在運(yùn)行時(shí)才能決定,這就屬于晚綁定(運(yùn)行時(shí)多態(tài))

  • 動(dòng)態(tài)多態(tài)產(chǎn)生條件
    • 先有繼承關(guān)系
    • 父類有虛函數(shù)
    • 子類重寫父類虛函數(shù)
    • 父類的指針或引用,指向子類對象
#include <iostream>
using namespace std;
#include <cstring>
class Animal
{
public:
    virtual void speak()
    {
        cout << "動(dòng)物在說話" << endl;
    }
};
class Cat : public Animal
{
public:
    void speak()
    {
        cout << "小貓?jiān)谡f話" << endl;
    }
};
//對于有父子類關(guān)系的類,指針或者引用是可以直接轉(zhuǎn)換的
void doSpeak(Animal &animal)
{
    // animal.speak();//動(dòng)物在說話    地址早綁定,屬于靜態(tài)聯(lián)編
    
    //如果想調(diào)用小貓說話,地址就不能早綁定;需要?jiǎng)討B(tài)聯(lián)編
    //只需要在Animal的speak函數(shù)前加 ,就成為虛函數(shù)就行了

    animal.speak();//小貓?jiān)谡f話
}
main()
{
    Cat cat;
    doSpeak(cat);
    return 0;
}
  • 虛函數(shù)原理


    1.png

父類有虛函數(shù)之后,本質(zhì)發(fā)生了變化,當(dāng)父類指向子類調(diào)用子類函數(shù)時(shí)候,
實(shí)際上通過虛函數(shù)表指針指針虛函數(shù)表,調(diào)用的就是實(shí)際的子類對象的函數(shù)
實(shí)際上就是通過偏移量去調(diào)用函數(shù)

  • 純虛函數(shù)和抽象類

抽象類無法實(shí)例化

class Animal
{
public:
    //純虛函數(shù)
    //如果一個(gè)類中包含了純虛函數(shù),那么這個(gè)類就無法實(shí)例化對象了
    //這個(gè)類我們通常稱為抽象類
    //抽象類的子類,必須重寫父類中的純虛函數(shù),否則也屬于抽象類
    virtual void getReuslt()=0;
};
  • 虛析構(gòu)和純虛析構(gòu)
    • 純虛函數(shù)是沒有實(shí)現(xiàn)的,純虛析構(gòu)類內(nèi)聲明類外實(shí)現(xiàn)
    • 如果一個(gè)類有個(gè)純虛析構(gòu)函數(shù),那么也是抽象類,無法實(shí)例化對象;但是子類不需要重寫(和純虛函數(shù)的區(qū)別)
#include <iostream>
using namespace std;
#include <cstring>
class Animal
{
public:
    Animal()
    {
        cout << "Animal構(gòu)造" << endl;
    }
    //虛析構(gòu)
    //如果子類中有指向堆區(qū)的屬性,那么要利用虛析構(gòu)技術(shù)再delete的時(shí)候調(diào)用子類的析構(gòu)函數(shù)
    virtual ~Animal()
    {
        cout << "Animal析構(gòu)" << endl;
    }
    //析構(gòu)不能重載不能有多個(gè),一般只寫一個(gè)
    //純虛析構(gòu):需要有聲明也需要有實(shí)現(xiàn);類內(nèi)聲明,類外實(shí)現(xiàn)
    // virtual ~Animal() = 0;

    virtual void speak()
    {
        cout << "動(dòng)物叫" << endl;
    }
};
// Animal::~Animal()
// {
//     cout << "Animal純虛析構(gòu)的調(diào)用" << endl;
// }

class Cat : public Animal
{
public:
    Cat(char *name)
    {
        cout << "Cat構(gòu)造" << endl;
        this->m_Name = new char[strlen(name) + 1];
        strcpy(this->m_Name, name);
    }
    virtual void speak()
    {
        cout << this->m_Name << "貓叫" << endl;
    }
    ~Cat()
    {
        cout << "Cat析構(gòu)" << endl;
        if (this->m_Name)
        {
            delete[] this->m_Name;
            this->m_Name = NULL;
        }
    }
    char *m_Name;
};

main()
{
    //多態(tài)形式把子類屬性創(chuàng)建再堆區(qū)的
    //那么父類析構(gòu)不會(huì)調(diào)用,需要把父類析構(gòu)也加上virtual才會(huì)被調(diào)用
    Animal *a = new Cat("Tom");
    a->speak();
    delete a;

    //有純虛析構(gòu),也是抽象類無法實(shí)例化
    // Animal aa;
    return 0;
}
// Animal構(gòu)造
// Cat構(gòu)造
// Tom貓叫
// Cat析構(gòu)
// Animal析構(gòu)
  • 向上向下類型轉(zhuǎn)換
2.png
  • 因?yàn)樽宇惪赡苡袛U(kuò)展屬性/函數(shù),所以內(nèi)存指針范圍更大;那么怎么轉(zhuǎn)換才安全呢?
    • 左邊父右邊子(子轉(zhuǎn)父)
    • 最開始new的就是子,最終怎么轉(zhuǎn)都是正常的

重寫,重載,重定義

  • 重載:同一個(gè)作用域的同名函數(shù)
    • 同一個(gè)作用域
    • 參數(shù)個(gè)數(shù),參數(shù)順序,參數(shù)類型不同
    • 和函數(shù)返回值無關(guān)
    • const也可以作為重載條件 //do(const T t) do(T t)
  • 重定義(隱藏)/c++中重定義類似java中重寫
    • 有繼承
    • 子類重新定義父類的同名成員(非virtual函數(shù))
  • 重寫(覆蓋)
    • 有繼承
    • 子類重寫父類的virtual函數(shù)
    • 函數(shù)返回值,函數(shù)名字,函數(shù)參數(shù),必須和基類中的虛函數(shù)一致

泛型編程

模板

c++提供兩種模板機(jī)制:函數(shù)模板和類模板

函數(shù)模板

#include <iostream>
using namespace std;
#include <cstring>

//利用模板實(shí)現(xiàn)通用交換函數(shù):而且緊跟著的函數(shù)/類才能使用當(dāng)前模板,后面函數(shù)就需要重新書寫模板了
template <typename T>
void mySwap(T &a, T &b)
{
    T temp = a;
    a = b;
    b = temp;
}


//模板不能單獨(dú)使用,必須指定出T才能使用
template <typename T>
void mySwap2()
{
    T a;
}

int main()
{
    int a = 10;
    int b = 20;
    //1. 自動(dòng)類型推導(dǎo)
    mySwap(a, b);
    cout << a << b << endl; //2010
    //2.顯示指定類型
    mySwap<int>(a, b);
    cout << a << b << endl; //1020

    //雖然沒有參數(shù),但是T在函數(shù)內(nèi)部已經(jīng)存在,沒有指定類型也沒有自動(dòng)推導(dǎo),所以內(nèi)存怎么分配呢,所以不能這樣使用
    // mySwap2();//錯(cuò)誤
    //模板不能單獨(dú)使用,必須指定出T才能使用
     mySwap2<int>();//可以
    return 0;
}
  • 通用排序函數(shù),實(shí)現(xiàn)對char和int數(shù)組的排序
#include <iostream>
using namespace std;
#include <cstring>
template <typename T>
void mySwap(T &a, T &b)
{
    T temp = a;
    a = b;
    b = temp;
}

//通用排序函數(shù),實(shí)現(xiàn)對char和int數(shù)組的排序
template <class T> //typename和class效果完全一樣;
void mySort(T arr[], int len)
{
    for (int i = 0; i < len; i++)
    {
        int max = i;
        for (int j = i + 1; j < len; j++)
        {
            if (arr[max] < arr[j])
            {
                max = j;
            }
        }
        //判斷算出的max和開始認(rèn)定的i是否一致,如果不同則交換數(shù)據(jù)
        if (i != max)
        {
            mySwap(arr[i], arr[max]);
        }
    }
}

template <class T>
void printArray(T arr[], int len)
{
    for (int i = 0; i < len; i++)
    {
        cout << arr[i] << endl;
    }
}

void test01()
{
    // char charArray[] = "helloworld";
    // int len = strlen(charArray);
    // mySort(charArray, len);
    // printArray(charArray, len);

    int intArray[] = {5, 7, 1, 4, 2, 3};
    int len = sizeof(intArray)/sizeof(int);
    mySort(intArray, len);
    printArray(intArray, len);
}
int main()
{
    test01();
    return 0;
}
  • 函數(shù)模板和普通函數(shù)的區(qū)別以及調(diào)用規(guī)則
#include <iostream>
using namespace std;
#include <cstring>
template <class T>
T myAdd(T a, T b)
{
    return a + b;
}

int myAdd2(int a, int b)
{
    return a + b;
}

void test01()
{
    int a = 10;
    int b = 20;

    char c = 'c';
    // myAdd(a,c);//如果使用自動(dòng)類型推導(dǎo),是不會(huì)發(fā)生隱式類型轉(zhuǎn)換的,會(huì)報(bào)錯(cuò)
    myAdd2(a, b); //普通函數(shù)會(huì)發(fā)生普通類型轉(zhuǎn)換

    myAdd<int>(a, b); //指定類型,可以進(jìn)行隱式類型轉(zhuǎn)換
}
//2. 函數(shù)模板和普通函數(shù)的調(diào)用規(guī)則
template <class T>
void myPrint(T a, T b)
{
    cout << "函數(shù)模板調(diào)用" << endl;
}


template <class T>
void myPrint(T a, T b,T c)
{
    cout << "函數(shù)模板三個(gè)參數(shù)調(diào)用" << endl;
}

void myPrint(int a, int b)
{
    cout << "普通函數(shù)調(diào)用" << endl;
}
void test02()
{
    int a = 10;
    int b = 20;
    //1. 如果函數(shù)模板和普通函數(shù)都可以調(diào)用,優(yōu)先調(diào)用普通函數(shù),因?yàn)樾阅芨?    myPrint(a, b); //普通函數(shù)調(diào)用

    //2. 如果強(qiáng)制調(diào)用函數(shù)模板,可以使用空模板參數(shù)列表
    myPrint<>(a, b);//函數(shù)模板調(diào)用

    //3. 函數(shù)模板也可以發(fā)生函數(shù)重載
    myPrint<>(a, b,10);//函數(shù)模板三個(gè)參數(shù)調(diào)用

    //4. 如果函數(shù)模板能產(chǎn)生更好匹配,優(yōu)先使用函數(shù)模板
    //例如:此時(shí)如果是普通函數(shù),還要char轉(zhuǎn)int,所以就不是更好的匹配,所以優(yōu)先使用函數(shù)模板
    char c='c';
    char d='d';
    myPrint(c,d);//函數(shù)模板調(diào)用
}
int main()
{
    test02();
    return 0;
}
  • 模板機(jī)制和模板局限性
    • 模板機(jī)制
      • 編譯器并不是把函數(shù)模板處理成能夠處理任何類型的函數(shù),例如自定義類型
      • 函數(shù)模板通過具體類型產(chǎn)生不同的函數(shù),生成的函數(shù)成為模板函數(shù)
      • 編譯器會(huì)對函數(shù)模板進(jìn)行二次編譯,在聲明的地方對模板代碼本身進(jìn)行編譯,在調(diào)用的地方對參數(shù)替換后的代碼進(jìn)行編譯
    • 模板局限性

      編寫的模板函數(shù)很可能無法處理某些類型,例如T是Person怎么> =比較;另一方面,有時(shí)候通用化是有意義的,但C++語法不允許這樣做。
      為了解決這種問題,可以提供模板的重載,為這些特定的類型提供具體化的模板

//利用具體換技術(shù)實(shí)現(xiàn),或者也可以通過運(yùn)算符重載實(shí)現(xiàn)
template <>bool myCompare(Person &a, Person &b)
{
    if (a.name==b.name) return true
}

類模板

  • 基本語法
#include <iostream>
using namespace std;
#include <cstring>
template <class T, class D=int> //可以有默認(rèn)值,則下面就可以不指定這個(gè)
class Person
{
public:
    Person(T name, D age)
    {
        this->name = name;
        this->age = age;
    }
    T name;
    D age;
};
void test01()
{
    //自動(dòng)類型推導(dǎo):類模板不可以使用類型推導(dǎo)
    // Person p1("John",100);//錯(cuò)誤

    //正確:顯示指定類型
    Person<string> p1("John", 1);
}

int main()
{
    test01();
    return 0;
}
  • 成員函數(shù)創(chuàng)建時(shí)機(jī)
#include <iostream>
using namespace std;
#include <cstring>
class Person1
{
public:
    void showPerson1()
    {
        cout << "person1 show 調(diào)用" << endl;
    }
};
class Person2
{
public:
    void showPerson2()
    {
        cout << "person2 show 調(diào)用" << endl;
    }
};

template <class T>
class MyClass
{
public:
    void func1()
    {
        obj.showPerson1();
    }
    void func2()
    {
        obj.showPerson2();
    }
    T obj;
};
void test01()
{
    MyClass<Person1> p1;
    p1.func1();
    //類模板中的成員函數(shù)并不是一開始創(chuàng)建出來的,而是運(yùn)行階段確定出T的數(shù)據(jù)類型才創(chuàng)建的
    // p1.func2();//此處是調(diào)用失敗的

    MyClass<Person2> p1;
    p1.func2();
}

int main()
{
    test01();
    return 0;
}
  • 類模板做函數(shù)參數(shù)
#include <iostream>
using namespace std;
#include <cstring>
template <class T1, class T2>
class Person
{
public:
    Person(T1 name, T2 age)
    {
        this->name = name;
        this->age = age;
    }
    void shwoPerson()
    {
        cout << "姓名:" << this->name << "年齡:" << this->age << endl;
    }
    T1 name;
    T2 age;
};
//1. 指定傳入的類型
void doWork(Person<string,int> &p){
    p.shwoPerson();
}
//2. 參數(shù)模板化
template<class T1, class T2>
void doWork2(Person<T1,T2> &p){
    p.shwoPerson();
}
//3.整個(gè)類模板化
template<class T>
void doWork3(T &p){
    p.shwoPerson();
}

void test01()
{
    Person<string,int> p("Hello",999);
    doWork(p);
    //因?yàn)镻erson<string,int>會(huì)把類型傳遞到模板上template<class T1, class T2>,則doWork2自然知道類型
    doWork2(p);
    doWork3(p);
}

int main()
{
    test01();
    return 0;
}
  • 類模板的繼承的寫法
#include <iostream>
using namespace std;
#include <cstring>
//形式一:寫死了,不推薦
template <class T>
class Base
{
public:
    T m_A;
};
//指定類型,父類才能知道T類型,然后才能給子類分配內(nèi)存
class Son : public Base<int>
{

};


//形式二
template <class T>
class Base1
{
public:
    T m_A;
};
template <class T1, class T2>
class Son1 : public Base1<T2>
{
public:
    T1 m_B;
};

void test01()
{
    Son1<int,double>s;
}

int main()
{
    test01();
    return 0;
}
  • 類模板中成員函數(shù)類外實(shí)現(xiàn)
#include <iostream>
using namespace std;
#include <cstring>
template <class T1, class T2>
class Person
{
public:
    Person(T1 name, T2 age);
    // {
    //     this->m_A=name;
    //     this->m_B=age;
    // }
    void showPerson();
    T1 m_A;
    T2 m_B;
};

template <class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
    this->m_A = name;
    this->m_B = age;
}

template <class T1, class T2>
void Person<T1, T2>::showPerson()
{
}

void test01()
{
    Person<string, int> p("hello", 100);
}

int main()
{
    test01();
    return 0;
}
  • 類模板中的友元
    • 類內(nèi)實(shí)現(xiàn)
#include <iostream>
using namespace std;
#include <cstring>

template <class T1, class T2>
class Person
{
public:
    //友元函數(shù),類內(nèi)的實(shí)現(xiàn);其實(shí)本質(zhì)上還是全局函數(shù),所以下面是直接調(diào)用
    friend void printPerson(Person<T1, T2> &p)
    {
        cout << p.m_A << endl;
    }

    Person(T1 name, T2 age)
    {
        this->m_A = name;
        this->m_B = age;
    }
private:
    T1 m_A;
    T2 m_B;
};

void test01()
{
    Person<string, int> p("John", 12);
    printPerson(p);
}

int main()
{
    test01();
    return 0;
}
  • 類外實(shí)現(xiàn)(較復(fù)雜)
//方式一:略復(fù)雜
#include <iostream>
using namespace std;
#include <cstring>

//函數(shù)模板的聲明
template <class T1, class T2>
class Person;

//針對類外實(shí)現(xiàn)需要提前聲明,又因?yàn)檫@里用到了Person,所以也需要把person聲明提前告訴編譯器
template <class T1, class T2>
void printPerson1(Person<T1, T2> &p);




template <class T1, class T2>
class Person
{
public:
    //友元函數(shù),類外實(shí)現(xiàn);加上<>其實(shí)就是告訴編譯器,是模板函數(shù)而不是普通函數(shù),和外面對應(yīng)起來,不然編譯器當(dāng)成普通函數(shù)是找不到類外的實(shí)現(xiàn)
    //因?yàn)轭愅馐悄0搴瘮?shù)實(shí)現(xiàn)
    friend void printPerson1<>(Person<T1, T2> &p);

    Person(T1 name, T2 age)
    {
        this->m_A = name;
        this->m_B = age;
    }
private:
    T1 m_A;
    T2 m_B;
};

template <class T1, class T2>
void printPerson1(Person<T1, T2> &p)
{
    cout <<"類外"<< p.m_A << endl;
}

void test01()
{
    Person<string, int> p("John", 12);
    printPerson1(p);
}

int main()
{
    test01();
    return 0;
}
//方式二:簡單一點(diǎn)
#include <iostream>
using namespace std;
#include <cstring>

template <class T1, class T2>
class Person;

//聲明實(shí)現(xiàn)在一起
template <class T1, class T2>
void printPerson1(Person<T1, T2> &p)
{
    cout << "類外" << p.m_A << endl;
}
template <class T1, class T2>
class Person
{
public:
    friend void printPerson1<>(Person<T1, T2> &p);
    Person(T1 name, T2 age)
    {
        this->m_A = name;
        this->m_B = age;
    }

private:
    T1 m_A;
    T2 m_B;
};

void test01()
{
    Person<string, int> p("John", 12);
    printPerson1(p);
}

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

類型轉(zhuǎn)換

  • 靜態(tài)轉(zhuǎn)換
#include <iostream>
using namespace std;
#include <cstring>

//1. 靜態(tài)類型轉(zhuǎn)換
void test01()
{
    //允許內(nèi)置數(shù)據(jù)類型之間的轉(zhuǎn)換
    char a = 'a';
    double d = static_cast<double>(a);
    cout << d << endl; //97
}

class Base
{
};
class Son : public Base
{
};
class Other
{
};
void test02()
{
    Base *base = NULL;
    Son *son = NULL;
    //將base轉(zhuǎn)son,父轉(zhuǎn)子,向下類型轉(zhuǎn)換,不安全
    Son *son2 = static_cast<Son *>(base);
    //子轉(zhuǎn)父
    Base *base2=static_cast<Base *>(son);
    //base轉(zhuǎn)為other:無效且錯(cuò)誤,這種轉(zhuǎn)換必須有父子關(guān)系
    Other *other=static_cast<Other *>(base);
}

int main()
{
    test01();
    return 0;
}
  • 動(dòng)態(tài)轉(zhuǎn)換

具有類型檢查的功能,比靜態(tài)轉(zhuǎn)換更安全

#include <iostream>
using namespace std;
#include <cstring>

void test01()
{
    //不允許內(nèi)置數(shù)據(jù)類型之間的轉(zhuǎn)換;因?yàn)榭赡芫葋G失
    // char a = 'a';
    // double d = dynamic_cast<double>(a);
    // cout << d << endl;
}

class Base
{
};
class Son : public Base
{
};
class Other
{
};
void test02()
{
    Base *base = NULL;
    Son *son = NULL;
    // Son *son2 = dynamic_cast<Son *>(base); //不安全不允許
    Base *base2 = dynamic_cast<Base *>(son); //安全是允許的
    // Other *other = dynamic_cast<Other *>(base); //無關(guān)系,不允許
}

//多態(tài)相關(guān)轉(zhuǎn)換
class Base1
{
    virtual void func() {}
};
class Son1 : public Base1
{
    void func() {}
};
void test03()
{
    //如果發(fā)生多態(tài),那么轉(zhuǎn)換總是安全的:如下寫法
    Base1 *base = new Son1;
    Son1 *son = NULL;
    Son1 *son1 = dynamic_cast<Son1 *>(base); 
}
int main()
{
    test01();
    return 0;
}

常量轉(zhuǎn)換

不能直接對非指針和非引用的變量使用const_cast操作符去直接移除它的const

void test01()
{
    const int *p=NULL;
    int *pp=const_cast<int*>(p);
    const int *ppp=const_cast<const int*>(pp);
}
int main()
{
    test01();
    return 0;
}

重新解釋轉(zhuǎn)換

是最不安全的一種轉(zhuǎn)換機(jī)制,主要用于將一種數(shù)據(jù)類型從一種類型轉(zhuǎn)化為另一種類型。它可以將一個(gè)指針轉(zhuǎn)換成一個(gè)整數(shù),也可以將一個(gè)整數(shù)轉(zhuǎn)換成指針

int a=10;
int *p=reinterpret_cast<int*>(a);

異常

如果多層捕獲異常,默認(rèn)是最里面的捕獲,但是可以在子異常中直接寫throw來向上傳遞

異常必須有函數(shù)進(jìn)行處理,如果不處理,程序會(huì)自動(dòng)調(diào)用terminate函數(shù),讓程序崩潰中斷

catch(...){
    throw;
}
#include <iostream>
using namespace std;
#include <cstring>
class MyException
{
public:
    void printError()
    {
        cout << "自定義異常" << endl;
    }
}

int
myDiv(int a, int b)
{
    if (b == 0)
    {
        // return -1;
        // throw -1; //返回int類型的異常,而不是-1
        // throw 'c'; //返回char類型異常
        

        //棧解旋:可通過p1,p2的構(gòu)造析構(gòu)去看
        //從try代碼塊開始,到throw拋出異常之前,所有棧上的數(shù)據(jù)都會(huì)被釋放掉
        //釋放的順序和構(gòu)造順序是相反的,這個(gè)過程成為棧解旋
        Person p1;
        Person p2;

        throw new MyException(); //拋出MyException匿名對象
    }
    return a / b;
}
int main()
{
    try
    {
        myDiv(1, 0);
    }
    catch (int)
    {
        cout << "int類型異常捕獲" << endl;
    }
    catch (char)
    {
        cout << "char類型異常捕獲" << endl;
    }
    catch (MyException e)
    {
        e.printError();
    }
    catch (...)
    {
        cout << "其他類型異常捕獲" << endl;
    }
    return 0;
}

異常的接口聲明

qt和linux下是正確的,vs并沒有提供這種機(jī)制

void func() throw(int)
// void func() throw(...)
{
    //只允許拋出int
    throw 1;
}

int main()
{
    try
    {
        func();
    }
    catch (int)
    {
    }
    return 0;
}
  • 異常變量的生命周期
#include <iostream>
using namespace std;
#include <cstring>
class MyException
{
public:
    MyException()
    {
        cout << "MyException默認(rèn)構(gòu)造" << endl;
    }
    MyException(const MyException &e)
    {
        cout << "MyException拷貝構(gòu)造" << endl;
    }
    ~MyException()
    {
        cout << "MyException析構(gòu)函數(shù)" << endl;
    }
    void printError()
    {
        cout << "自定義異常" << endl;
    }
};

void doWork()
{
    //調(diào)用默認(rèn)構(gòu)造
    throw MyException();

    // throw new MyException();//只會(huì)調(diào)用默認(rèn)構(gòu)造,但是需要自己管理釋放內(nèi)存delete
}
int main()
{
    try
    {
        doWork();
    }
    catch (MyException e) //調(diào)用拷貝構(gòu)造,效率低
    // catch (MyException &e)  //效率高一些,推薦
    {
        e.printError();
    }
    catch (...)
    {
        cout << "其他類型異常捕獲" << endl;
    }
    return 0;
}
//打印結(jié)果
// MyException默認(rèn)構(gòu)造
// MyException拷貝構(gòu)造
// 自定義異常
// MyException析構(gòu)函數(shù)
// MyException析構(gòu)函數(shù)


//如果:catch (MyException &e) 傳入引用
// MyException默認(rèn)構(gòu)造
// 自定義異常
// MyException析構(gòu)函數(shù)
  • 異常的多態(tài)
#include <iostream>
using namespace std;
#include <cstring>
class BaseException
{
public:
    virtual void printError() = 0;
};

class NULLPointException : public BaseException
{
public:
    void printError()
    {
        cout << "NULLPointException異常" << endl;
    }
};

void doWork()
{
    throw NULLPointException();
}
int main()
{
    try
    {
        doWork();
    }
    catch (BaseException &e)
    {
        e.printError();
    }
    return 0;
}
  • 系統(tǒng)標(biāo)準(zhǔn)異常使用
//注意
#include <stdexcept> //異常需要這個(gè)頭文件

void doWork()
{
    //系統(tǒng)異常:還有很多其他內(nèi)置系統(tǒng)異常
    throw out_of_range("越界異常");
}
int main()
{
    try
    {
        doWork();
    }
    catch (out_of_range &e)
    {
        cout<<e.what()<<endl; //越界異常
    }
    return 0;
}

標(biāo)準(zhǔn)輸入輸出流

1.png
  • 輸入流
int main()
{

    //利用cin.get取出數(shù)據(jù)時(shí)候,換行符會(huì)遺留在緩沖區(qū)中,例如get(buf,1024;之后再次cin.get()才可以取出換行符
    // cin.get();//一次只能讀取一個(gè)字符
    // cin.get(一個(gè)參數(shù));//讀一個(gè)字符串
    // cin.get(兩個(gè)參數(shù));//可以讀字符串  cin.get(buf,1024);
    // cin.getline();//不同于get,這個(gè)取出了換行符
    // cin.ignore(); //忽略,默認(rèn)忽略一個(gè)字符
    // cin.ignore(2); //忽略,忽略前兩個(gè)字符,同理參數(shù)x就代表忽略多少個(gè)字符
    // cin.peek();//偷窺,就是只是看看,如果之后繼續(xù)get還是從上次get的開始讀
    //cin.fail();//緩沖區(qū)狀態(tài)  0代表正常,1代表異常
    // cin.putback();//放回,就是放回原來位置好像沒有進(jìn)行任何操作一樣

    char c = cin.get();
    cin.putback(c);
    char buf[1024] = {0};
    cin.getline(buf, 1024);
    cout << buf << endl;//最終發(fā)現(xiàn),輸入什么輸出什么,沒有因?yàn)閏in.get()產(chǎn)生任何影響
    return 0;
}
  • 輸出流
int main()
{

    //    cout.flush();//刷新緩沖區(qū)linux下有效
    //    cout.put('h');//向緩沖區(qū)寫字符
    //    cout.write();//從buffer中寫num個(gè)字節(jié)到當(dāng)前輸出流中
    cout.put('h').put('e');
    char buf[] = "hello world";
    cout.write(buf, strlen(buf));

    cout<<"hello world"<<endl;//這才是最常用

    //流成員函數(shù)格式化輸出
    int num =99;
    cout.width(20);//指定寬度為20
    cout.fill('*');//填充
    cout.setf(ios:left);//左對齊
    cout.unsetf(ios:dec);//卸載十進(jìn)制
    cout.setf(ios:hex);//安裝十六進(jìn)制
    cout.setf(ios:showbase);//顯示基數(shù)
    cout.unsetf(ios:hex);//卸載十六進(jìn)制
    cout.setf(ios:oct);//安裝八進(jìn)制


    //使用控制符格式化輸出
    //include <iomanip>
    int num1=99;
    cout<<setw(20) //設(shè)置寬度
        <<setfill('~')//設(shè)置填充
        <<setiosflags(ios::showbase) //顯示基數(shù)
        <<setiosflags(ios::left)//設(shè)置左對齊
        <<hex //顯示十六進(jìn)制
        <<number
        <<endl;
    return 0;
}

讀寫文件

有很多種形式,參考即可

#include <fstream>
#include <iostream>
using namespace std;
 
int main ()
{
   char data[100];
   // 以寫模式打開文件
   ofstream outfile;
   outfile.open("afile.dat");
   cout << "Writing to the file" << endl;
   cout << "Enter your name: "; 
   cin.getline(data, 100);
   // 向文件寫入用戶輸入的數(shù)據(jù)
   outfile << data << endl;
   cout << "Enter your age: "; 
   cin >> data;
   cin.ignore();
   // 再次向文件寫入用戶輸入的數(shù)據(jù)
   outfile << data << endl;
   // 關(guān)閉打開的文件
   outfile.close();
   // 以讀模式打開文件
   ifstream infile; 
   infile.open("afile.dat"); 
   cout << "Reading from the file" << endl; 
   infile >> data; 
   // 在屏幕上寫入數(shù)據(jù)
   cout << data << endl;
   // 再次從文件讀取數(shù)據(jù),并顯示它
   infile >> data; 
   cout << data << endl; 
   // 關(guān)閉打開的文件
   infile.close();
   return 0;
}
  • 文件位置指針
// 定位到 fileObject 的第 n 個(gè)字節(jié)(假設(shè)是 ios::beg)
fileObject.seekg( n );
 
// 把文件的讀指針從 fileObject 當(dāng)前位置向后移 n 個(gè)字節(jié)
fileObject.seekg( n, ios::cur );
 
// 把文件的讀指針從 fileObject 末尾往回移 n 個(gè)字節(jié)
fileObject.seekg( n, ios::end );
 
// 定位到 fileObject 的末尾
fileObject.seekg( 0, ios::end );
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容