c++踩坑大法好 typedef和模板


1,typedef字面意思,自定義一種數據類型

語法:typedef 類型名稱 類型標識符;

1),基本用法:

(1) 為基本數據類型定義新的類型名。

(2) 為自定義數據類型(結構體、公用體和枚舉類型)定義簡潔的類型名稱。

(3) 為數組定義簡潔的類型名稱。

(4) 為指針定義簡潔的名稱。

簡單使用實例:

int main() {
    using namespace std;
    typedef int hehe;
    //相當於定義一個新的數據類型類型
    hehe a = 12;
    hehe(bb) = 34;
    //這兩種實現方法是一樣的效果,語法而已
    printf("%d\n,%d",a,bb);
    typedef int f();
    //相當於定義一個返回int的函數f
    f(daqing);
    //等價於聲明int daqing(),相當於daqing這個變量也是一個函數名稱,實現在main后面
    int test = daqing();
    printf("test%d",test);
    return 0;
}
int daqing() {
    return 99;
}

2),typedef結構體語法,並且取別名

using namespace std;

typedef struct node {
    int data;
    char test;
}tree;
//聲明一個結構體struct node的別名為tree
int main() {
    tree atree;
    atree.data = 1;
    //根據結構體的別名初始化變量

    node hehe;
    hehe.data = 3;
    //根據結構的本名初始化變量

    return 0;
}

3),typedef聲明指針類型

實例1,

using namespace std;

typedef int hehe, hehe2;
//此處可以理解為聲明了兩種數據類型,一個是hehe類,另一個是hehe2類,兩者實際上都是int
typedef int hehe3, *hehe4;
//此處聲明了兩種數據類型,一種是hehe3類,實際上就是int,另一個是hehe4這個指針數據類型,如果把*hehe4看成一個整體,
//那么這個整體儲存的是指向實體的指針,而hehe4是存儲的是實體的地址
int main() {
    hehe a = 1;
    hehe2 b = 2;
    //分別創建了a和b作為結構的實體

    hehe3 he3 = 3;
    //創建了he3為hehe3的結構實體
    int aa = 1;
    hehe4 bb = &aa;
    //創建bb為hehe4這個指針數據類型的結構實體,因為hehe4是一個指針數據類型,所以bb必然也是一個指針,必須按照指針的規則賦值

    return 0;
}

實例二,看我typedef一個結構體指針

using namespace std;
typedef struct {
    int    data;
    LNode   *next;  
}LNode, *LinkList;
//一個上述的struct聲明了一種特殊類型LinkList,這個類型表示變量是一個指針

int main() {
    LNode hehe;
    LinkList daqing = &hehe;
    //LinkList數據類型是一個指針數據類型,所以,意思是,聲明一個指向LinkList數據類型的指針,然后把已經初始化的LNode類型的變量hehe的地址賦值給這個地址
    return 0;
}

 

2,模板(函數模板)

1),模板簡單理解:

首先模板是針對編譯器使用的,它就是告訴編譯器如何定義函數,比如如下的例子:

template <typename T>     
//聲明一個模板,第一個參數的固定的,模板名叫T
void Swap(T &a,T &b){
    //省略
}

當int變量需要使用Swap的時候,T就變成了int,如果是double變量要使用該函數,T就變成了double,所以說,對計算機來說,計算量絲毫沒少。

模板允許只定義一次函數的實現,即可使用不同類型的參數來調用該函數。這樣做可以減小代碼的書寫的復雜度,同時也便於修改。

 

c++中模板存在的意義:

如果是python,想要交換兩個變量的內容:

def exchange(x,y):
    a=x;
    x=y;
    y=a;
    return (x,y)
#整數交換 
x,y=1,10
x,y=exchange(x,y)
print(x,y)
#字符串交換
x,y="a","bcd"
x,y=exchange(x,y)
print(x,y)

但是如果是c++,這樣做明顯是不行的,本人菜鳥,寫出交換兩個整數的代碼如下:

void daqing(int *x,int *y);
void daqing(int *x,int *y) {
    int a = *x;
    *x = *y;
    *y = a;
}
int main() {
    using namespace std;
    int a = 1;
    int b = 2;
    daqing(&a,&b);
//此處相當於把a,b的地址傳遞給了daqing函數,而daqing函數拿到的是*&a,*&b,(x和y相當於&a,&b)相當於a和b的值,剛開始*x=1,*y=2,int a 作為局部變量保存了*x的值,1,然后x,y交換。
    printf("%d,%d\n",a,b);
    return 0;
}

如果想交換兩個char或者double,那就得把代碼copy一遍,然后把聲明和實現的代碼中的類型全都變了,好費勁啊,所以這時候我們就需要模板啦。

書上的實例:

#include "pch.h"
using namespace std;
template <typename AnyType>
//電腦電腦,我要建立一個模板,模板名稱是AnyType,關鍵字template和typename是必須的
void Swap(AnyType &a, AnyType&b);

int main() {
    int a = 1;
    int b = 2;
    Swap(a,b);
    printf("%d,%d\n",a,b);
    double aa = 10;
    double bb = 20;
    Swap(aa,bb);
    printf("%f,%f\n", aa, bb);
    string aaa = "12";
    string bbb = "abc";
    Swap(aaa,bbb);
    cout << aaa << " " << bbb << endl;
    return 0;
}

template <typename AnyType>
void Swap(AnyType &a, AnyType&b) {
    AnyType temp;
    temp = a;
    a = b;
    b = temp;
}
//模板我來理解大約是這么個意思,就是告訴電腦,我要新建一個臨時類型,類型名是自己定義的,比如anytype,等到需要用的時候,如果用的是int,那就用int代替anytype,如果是char,就用char代替anytype

 注意,函數模板不能縮短可執行程序,我的理解是,swap函數確實生成了int版本的函數,double版本和string版本,並非只有一個函數兼容了不同類型,所以對電腦來說計算量絲毫沒有少哦。而模板的好處是,生成多個函數的定義更加可靠,簡單。

2),模板不影響重載

//以下生命方法是沒問題的,實現省略了,調用swap函數的時候,傳入的參數符合哪一個重載函數,就使用哪個
template<typename T>
void Swap(T &a,T &b);

template<typename T>
void Swap(T a[], T b[], int n);

3),顯式具體化

個人理解:一個函數模板,可以生成int,double,string等多種不同的具體函數,可以針對某一種特殊的類型進行特殊的操作,比如swap這個模板函數,一般情況下實現a和b的互換,它對job結構進行了顯式具體化以后,就可以實現a的某個屬性和b的某個屬性互換了。嗯嗯

#include <iostream>
using namespace std;
struct job {
    char name[10];
    double salary;
    int floor;
};

template<typename T>
void Swap(T &a,T &b);
//以上是普通聲明

template<> void Swap<job>(job &j1, job &j2);
//這是一個顯式具體化的聲明,意思是:
//不要使用swap模板類生成函數定義,應該使用專門為int類型顯示地定義int模板來實現這個函數
void show(job& j);


int main() {
    int i, j;
    i = 10;
    j = 20;
    Swap(i,j);
    //此處使用隱式實例化,使用模板生成函數定義,模板通過傳入的參數i和j判斷需要用int,生成了swap的一個int實例
    job sue, sidney;
    sue = { "sue",73.23,1 };
    sidney = { "dsidney",55.23,2 };
    Swap(sue, sidney);
    //如果swap沒有那個重載的顯示具體化聲明,調用swap以后,sue和sidney會互換,但是sue的工資仍舊是73.23,仍在一樓,sidney也沒變,但是
    //既然已經有了針對job這個結構專門定義的job模板,所以系統會調用那個顯示具體化的swap函數,sue的工資會變成55.23,樓層會變成2樓。
    show(sue);
    //name is s salary 55.230000 floor 2
    return 0;
}

template<typename T>
void Swap(T &a, T &b) {
    T temp;
    temp = a;
    a = b;
    b = temp;
}
template<> void Swap<job>(job &j1, job &j2) {
    double temp1;
    int temp2;
    temp1 = j1.salary;
    temp2 = j1.floor;
    j1.salary = j2.salary;
    j1.floor = j2.floor;
    j2.salary = temp1;
    j2.floor = temp2;
}
void show(job &j){
    printf("name is %c salary %lf floor %d", j.name[0], j.salary, j.floor);
    //不想用cout,所以打印出來s是sue,d是sidney
}

4),顯式實例化

使用某個函數模板的時候,顯式地說告訴電腦,我需要一個某類型的模板函數(寫為這樣:add<double>(aa, bb)。),而不是讓電腦根據傳入的參數自己判斷。

#include <iostream>
using namespace std;

template<typename T>
T add(T a,T b);
//以上是普通聲明

int main() {
    double result;
    int aa = 1;
    double bb = 2;
    //result = add(aa, bb);
    //普通調用add會報錯,因為參數aa說T是int,bb說T是個double,然后系統就懵了,
    result = add<double>(aa, bb);
    //顯式地聲明add需要使用double生成模板,然后把aa強制轉換成double,所以這樣跑起來是沒問題的
    //這就是顯式實例化
    printf("%lf\n", result);

    return 0;
}
template<typename T>
T add(T a, T b) {
    return a + b;
}

 

3,類模板

1),模板基礎

 普通思想實現一個棧是這樣的(本應該頭文件和源文件分開,考慮到展示問題,干脆合起來了,請自行分開)

#include "pch.h"
using namespace std;
typedef unsigned long Item;
//定義一個類型,類型名是Item(實際上就是無符號整形),這樣寫的好處在於,unsigned long如果想變成int,可以直接改動一處。
class Stack
{
private:
    enum {MAX=10};
    //枚舉,此處相當於定義了一個整形MAX變量,
    Item items[MAX];
    //建立一個數組,數組長度為10,數組以Item類型填充
    int top;
public:
    Stack();
    //構造函數
    const bool isEmpty();
    const bool isfull();
    bool push(const Item &item);
    bool pop(Item &item);
    //注意,以上都是引用傳參,在函數內部修改參數值,不必return外部的參數也會變化
};
int main() {
    Stack zhan;
    Item a = 100;
    Item b = 200;
    Item c ;
    zhan.push(a);
    zhan.push(b);
    //添加兩個元素到棧里
    zhan.pop(c);
    //拿出棧頂的元素,元素值用變量c來存儲
    cout << c << endl;
    return 0;
}
Stack::Stack() {
    top = 0;
}
const bool Stack::isEmpty() {
    return top == 0;
}
const bool Stack::isfull() {
    return top == MAX;
}
bool Stack::push(const Item &item) {

    if (top < MAX) {
        items[top++] = item;
        //注意,此處的命令相當於top=top+1;items[top]=item;
        cout <<"push command,amount is:"<<top << endl;
        return true;
    }
    else
        return false;
}
bool Stack::pop(Item &item) {

    if (top>0) {
        item = items[--top];
        //注意,此處的命令相當於:item=items[top];top=top-1;千萬小心別理解錯了
        cout << "pop command,amount is:" <<top<< endl;
        return true;
    }
    else
        return false;
}

使用模板類實現的棧是這樣的:

#include "pch.h"
using namespace std;
template <class Type>
//定義一個叫Type的類模板和stack類緊緊關聯在一起,甚至分號都不用寫了,囧。。。
class Stack
{
private:
    enum {MAX=10};
    //枚舉,此處相當於定義了一個整形MAX變量,
    Type items[MAX];
    //建立一個數組,數組長度為10,數組以Item類型填充
    int top;
public:
    Stack();
    //構造函數
    bool isEmpty();
    bool isfull();
    bool push(const Type &item);
    bool pop(Type &item);
    //注意,以上都是引用傳參,傳遞的參數是模板,就是在函數內部修改參數值,不必return外部的參數也會變化
};
int main() {
    Stack<int> zhan;
    //注意,使用棧實例的時候就不能再寫Type這樣的模板代號了,要寫真正想要實例化的數據類型
    int a = 100;
    int b = 200;
    int c ;
    zhan.push(a);
    zhan.push(b);
    //添加兩個元素到棧里
    zhan.pop(c);
    //拿出棧頂的元素,元素值用變量c來存儲
    cout << c << endl;

    Stack<string> strzhan;
    //實例化一個string為模板類型的實例
    string aa = "abc";
    string bb = "aa0";
    string cc;
    strzhan.push(aa);
    strzhan.push(bb);
    //添加兩個元素到棧里
    strzhan.pop(cc);
    //拿出棧頂的元素,元素值用變量c來存儲
    cout << cc << endl;

    typedef double idouble;
    Stack<idouble> dbzhan;
    //實例化一個自己定義的類型為模板類型的實例,這樣竟然也可以,厲害厲害
    idouble aaa = 12;
    idouble bbb = 234;
    idouble ccc;
    dbzhan.push(aaa);
    dbzhan.push(bbb);
    //添加兩個元素到棧里
    dbzhan.pop(ccc);
    //拿出棧頂的元素,元素值用變量c來存儲
    cout << ccc << endl;
    return 0;
}
template <class Type>
Stack<Type>::Stack() {
    top = 0;
}
//注意,實現的時候,每個函數都需要加上模板信息,否則報錯,語法問題,記住就是了
//注意,普通函數實現的寫法是這樣的:Stack::Stack(){},但是使用了模板的函數實現的寫法是這樣的:Stack<Type>::Stack(){},尖括號用於說明,我是一個模板類
template <class Type>
bool Stack<Type>::isEmpty() {
    return top == 0;
}
template <class Type>
bool Stack<Type>::isfull() {
    return top == MAX;
}
template <class Type>
bool Stack<Type>::push(const Type &item) {
    if (top < MAX) {
        items[top++] = item;
        //注意,此處的命令相當於top=top+1;items[top]=item;
        cout <<"push command,amount is:"<<top << endl;
        return true;
    }
    else
        return false;
}
template <class Type>
bool Stack<Type>::pop(Type &item) {
    if (top>0) {
        item = items[--top];
        //注意,此處的命令相當於:item=items[top];top=top-1;千萬小心別理解錯了
        cout << "pop command,amount is:" <<top<< endl;
        return true;
    }
    else
        return false;
}

 2),多個參數的模板

因為模板是編譯器對某些特殊字符的替換,所以模板內帶的參數也可以是非常具體的數值,比如說,整數5,廢話不多說,看例子

#include <stdarg.h>
#include <iostream>
#include <string>
#include <memory>    //shared_ptr
#include <vector>
using namespace std;

template<class T, int n>
class Father {
private:
    T ar[n];
public:
    Father() {};
    explicit Father(const T &v);
    virtual T &operator[](int i);
    //virtual T operator[](int i) const;
};
template<class T, int n>
Father<T, n>::Father(const T &v) {
    for (int i = 0; i < n; i++) {
        ar[i] = v;
    }
}

template<class T, int n>
T &Father<T, n>::operator[](int i) {
    if (i<0 || i>n) {
        printf("out range");
        exit(-1);
    }
    return ar[i];
};
//template<class T, int n>
//T Father<T, n>::operator[](int i) const {
//    if (i<0 || i>n) {
//        printf("out range 2nd");
//        exit(-1);
//    }
//    return ar[i];
//}
int main(void)
{
    Father<double,5> f1(1.0);
    //編譯器定義了名為Father<double,5>的一個類,並且創建了該類的對象叫f1,傳入了參數1.0
    //該實例內部創建了一個長度為5內容都是1.0的double數組
    Father<double,6> f2(2.0);
    //編譯器定義了名為Father<double,6>的一個類,並且創建了該類的對象叫f2,傳入了參數2.0

    double hehe=f1.operator[](2);
    //把f1保存的數組中的數組的第二個元素拿出來看一下,,果然是1.0
    //這代碼有點傻啊,寫個例子還得定義一個重載函數,好吧被我注釋掉了,我是書上抄的。
    printf("%lf", hehe);

    return 0;
}

不過例子歸例子,以上這樣的寫法並不通用,因為模板參數每變一次就生成了新的class,不如 classname<int> instance(12)這樣的寫法通用


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM