zoukankan      html  css  js  c++  java
  • 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
    ,%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
    ",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
    ",a,b);
        double aa = 10;
        double bb = 20;
        Swap(aa,bb);
        printf("%f,%f
    ", 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
    ", 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)这样的写法通用

  • 相关阅读:
    R绘图学习笔记
    SVM与LR的比较
    精简android4.2
    vbox下android分辨率设置
    centos7命令
    mysql设置编码
    virutalbox虚拟机硬盘扩容
    spring boot学习
    SQL Server 2012清除连接过的服务器名称历史
    android AutoCompleteTextView和Spinner选中项加亮
  • 原文地址:https://www.cnblogs.com/0-lingdu/p/12269554.html
Copyright © 2011-2022 走看看