2024/01/14

回顾c++

目录

1. 回顾冒泡和选择排序

1.1 冒泡排序

1.2 选择排序

2. C++ auto 关键字

3. 模板

3.1 模板概念和特点

3.2 函数模板

3.2.1 语法:

3.2.2 函数模板两种调用方式:

3.2.3 注意事项

3.2.4 案例

3.2.5 普通函数和函数模板的隐式转换

3.2.6 普通函数和函数模板重载以及调用规则

例子3:空模板参数列表

函数模板重载:

更好的匹配会优先使用函数模板:

3.3 模板的局限性

3.4 类模板

3.4.1 类模板与函数模板区别:

3.4.2 类模板中成员函数创建时机

3.4.3 类模板对象做函数参数

3.4.4 类模板与继承

3.4.5 类模板成员函数类外实现

3.4.6 类模板分文件编写

3.4.7 类模板与友元


1. 回顾冒泡和选择排序

1.1 冒泡排序

从第一个元素开始两两比较,大的放在右侧。

所以每一轮循环都可以找到一个相对的最大值(最右侧),也就是需要len-1次循环。

每一小轮都是两两数据比较,那么需要比较len-1-i轮。

代码:

int main(int argc, char const *argv[])
{
    /* code */
    int arr[4] = {3,5,1,2};
    int length = sizeof(arr)/sizeof(int);
    for(int i = 0; i<length-1; i++)
    {
        for(int j = 0; j<length-1-i;j++)
        {
            if(arr[j]>arr[j+1])
            {
                int tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
            }
        }
    }

    for(int i = 0; i<length; i++)
    {
        cout<<arr[i]<<endl;
    }
    return 0;
}

1.2 选择排序

参考:排序算法:选择排序【图解+代码】_哔哩哔哩_bilibili

每次(从剩余的数组中)扫描选出一个最小(或者最大)的数,记录它的index,与数组的第一个值进行交换(相当于放到数组的最前面)。

选最大还是最小取决于需求是从大到小排还是从小到大排。

因为每个数字都要被扫描,所以扫描len轮。每一小轮从第i+1个数字开始扫描,一直扫描到最后的数字。

#include <iostream>
using namespace std;

void swap(int& a, int& b)
{
    int tmp = a;
    a = b;
    b = tmp;
}

void print_arr(int* arr, int n)
{
    for(int i = 0; i<n; i++)
    {
        cout<<arr[i]<<" ";
    }
    cout<<"\n";
}

int main(int argc, char const *argv[])
{
    /* code */
    int arr[4] = {3,5,1,2};
    int length = sizeof(arr)/sizeof(int);
    print_arr(arr,length);

    for(int i = 0; i<length; i++)
    {
        cout<<"i: "<<i<<endl;
        int min = i;
        cout<<"min: "<<min<<endl;
        for(int j = i+1; j<length; j++)
        {
            if(arr[j]<arr[min])
            {
                min = j;  
            }
        }
        cout<<"min: "<<min<<endl;

        swap(arr[min],arr[i]);
        print_arr(arr,length);
    }

    return 0;
}

2. C++ auto 关键字

c++ auto类型用法总结_c++怎么返回auto的类型-CSDN博客

3. 模板

C++泛型编程和STL技术。

3.1 模板概念和特点

通用的模具,提高复用性。

不可以直接使用,只是一个框架。通用并不是万能的。

泛型编程利用的技术就是模板。

两种模板机制:函数模板、类模板

3.2 函数模板

建立一个通用的函数,返回值类型和形参类型可以不具体指定,用虚拟的类型来代表。

3.2.1 语法:
template <typename T>
函数声明或定义

解释:

template:声明创建模板

typename:可以替换成class

T:通用的数据类型,名字可以替换,通常为大写

比如:

//交换两个整型
void swapInt(int& a, int& b)
{
    int c = a;
    a = b;
    b = c;
}
//交换两个浮点型
void swapInt(double& a, double& b)
{
    double c = a;
    a = b;
    b = c;
}
​
//声明一个模板,告诉编译器T使用一个通用数据类型
template<typename T>
void Swap(T &a, T &b)
{
    T temp = a;
    a = b;
    b = temp;
}

3.2.2 函数模板两种调用方式:

编译器自动类型推导

int main()
{
    int a = 10;
    int b = 20;
    Swap(a, b);
}

显示指定类型

int main()
{
    int a = 10;
    int b = 20;
    Swap<int>(a,b);
}

3.2.3 注意事项
  • 自动类型推导时,必须推导出一致的数据类型T,才可以使用。

template<typename T>
void Swap(T &a, T &b)
{
    T temp = a;
    a = b;
    b = temp;
}
​
int main(int argc, char const *argv[])
{
    /* code */
    int a = 10;
    int b = 20;
    char c = 'a';
​
    //Swap(a,b);    //正确
    //Swap(a,c);    //错误
    cout<<"a = "<<a<<endl;
    cout<<"b = "<<b<<endl;
    return 0;
}

  • 模板必须确定出T的数据类型,才可以使用。

template<typename T>
void Swap()
{
    cout<<"haha"<<endl;
}
int main()
{
    Swap();         //报错
    Swap<int>();    //不报错
}

3.2.4 案例
  • 函数模板封装一个排序的函数,实现对不同数据类型数组进行排序

  • 排序规则从大到小,算法为选择排序

  • 分别用inr和char数组进行测试

/*一个函数模板实现对int数组或者char数组进行排序*/
​
#include <iostream>
using namespace std;
​
template<typename T>
void my_Swap(T&a, T&b)
{
    T tmp = a;
    a = b;
    b = tmp;
}
​
template<typename T>
void my_Sort(T* arr, int len)
{
    for (int i = 0; i < len; i++)
    {
        int min_index = i;
        for (int j = i+1; j < len; j++)
        {
            if(arr[j] < arr[min_index])
            {
                min_index = j;
            }
        }
​
        //swap
        my_Swap(arr[min_index],arr[i]);
    }
}
​
int main(int argc, char const *argv[])
{
    /* code */
    int arr[4] = {3,5,1,2};
    my_Sort(arr,4);
​
    char arr1[4] = {'b','a','f','e'};
    my_Sort(arr1,4);
​
    for(int i = 0; i<4; i++)
    {
        cout<<arr1[i]<<endl;
    }
    return 0;
}
​

奇怪报错:当arr[min_index]误写成arr[min]时,报错是error: overloaded function with no contextual type information if(arr[j] < arr[min])

3.2.5 普通函数和函数模板的隐式转换

普通函数会自动进行隐式转换

int my_add(int a, int b)
{
    return a+b;
}
int main()
{
    int a = 10;
    int c = 'c';
    cout<<my_add(a,c)<<endl;    //不报错
}

函数模板,用自动类型推导,不可以发生隐式类型转换

函数模板,用显示指定类型,可以发生隐式类型转换

(但我的编译器可以自动隐式转换)

template<typename T>
T my_add(T a, T b)
{
    return a+b;
}
​
int main(int argc, char const *argv[])
{
    /* code */
    int a = 10;
    int b = 20;
    int c = 'c';
    cout<<my_add(a,c)<<endl;        //109 
    cout<<my_add<int>(a,c)<<endl;   //109
    return 0;
}

3.2.6 普通函数和函数模板重载以及调用规则
  • 如果都可以被调用,优先调用普通函数

  • 可以通过空模板参数列表来强制调用函数模板

  • 函数模板可以发生重载

  • 如果函数模板可以产生更好的匹配,优先调用函数模板

例子1:

#include <iostream>
using namespace std;
​
void my_print(int a, int b)
{
    cout<<"putong"<<endl;
    cout<<a+b<<endl;
    return;
}
​
template<typename T>
void my_print(T a, T b)
{
    cout<<"template"<<endl;
    cout<<a+b<<endl;
}
​
int main(int argc, char const *argv[])
{
    /* code */
    int a = 10;
    int b = 20;
    my_print(a,b);
    return 0;
}

输出:

putong
30

例子2:

#include <iostream>
using namespace std;
​
void my_print(int a, int b);
​
template<typename T>
void my_print(T a, T b)
{
    cout<<"template"<<endl;
    cout<<a+b<<endl;
}
​
int main(int argc, char const *argv[])
{
    /* code */
    int a = 10;
    int b = 20;
    my_print(a,b);
    return 0;
}

输出:报错:undefined reference to `my_print(int, int)'

例子3:空模板参数列表
#include <iostream>
using namespace std;
​
void my_print(int a, int b);
​
template<typename T>
void my_print(T a, T b)
{
    cout<<"template"<<endl;
    cout<<a+b<<endl;
}
​
int main(int argc, char const *argv[])
{
    /* code */
    int a = 10;
    int b = 20;
    //空模板参数列表
    my_print<>(a,b);
    return 0;
}

输出:

template
30

函数模板重载:
#include <iostream>
using namespace std;
​
template<typename T>
void my_print(T a, T b)
{
    cout<<"template"<<endl;
    cout<<a+b<<endl;
}
​
template<typename T>
void my_print(T a, T b, T c)
{
    cout<<"template"<<endl;
    cout<<a+b+c<<endl;
}
​
int main(int argc, char const *argv[])
{
    /* code */
    int a = 10;
    int b = 20;
    int c = 30;
    
    my_print<>(a,b,c);
    return 0;
}

输出:

template
60

但注意,如果是带默认参数的可能会报错

#include <iostream>
using namespace std;
​
template<typename T>
void my_print(T a, T b)
{
    cout<<"template"<<endl;
    cout<<a+b<<endl;
}
​
template<typename T>
void my_print(T a, T b, T c = 100)
{
    cout<<"template"<<endl;
    cout<<a+b+c<<endl;
}
​
int main(int argc, char const *argv[])
{
    /* code */
    int a = 10;
    int b = 20;
    int c = 30;
    
    my_print<>(a,b,c);
    return 0;
}

不报错。

#include <iostream>
using namespace std;
​
template<typename T>
void my_print(T a, T b)
{
    cout<<"template"<<endl;
    cout<<a+b<<endl;
}
​
template<typename T>
void my_print(T a, T b, T c = 100)
{
    cout<<"template"<<endl;
    cout<<a+b+c<<endl;
}
​
int main(int argc, char const *argv[])
{
    /* code */
    int a = 10;
    int b = 20;
    int c = 30;
    
    my_print<>(a,b);
    return 0;
}

报错:error: call of overloaded 'my_print(int&, int&)' is ambiguous my_print<>(a,b);

更好的匹配会优先使用函数模板:
void my_print(int a, int b)
{
    cout<<"putong"<<endl;
    cout<<a+b<<endl;
    return;
}
​
template<typename T>
void my_print(T a, T b)
{
    cout<<"template"<<endl;
    cout<<a+b<<endl;
}
​
int main(int argc, char const *argv[])
{
    /* code */
    char a = 'a';
    char b = 'b';
    my_print(a,b);
    return 0;
}

输出:

template
195

3.3 模板的局限性

并不是完全通用

template<typename T>
void f(T a, T b)
{
    cout<<a+b<<endl;
}

如果传入两个数组就不行了

为特定类型提供具体化的模板:

/*模板的局限性*/
​
#include <iostream>
using namespace std;
​
class Person
{
public:
    Person(int age)
    {
        m_age = age;
    }
    int m_age;
};
​
template<typename T>
bool my_cmp(T &a, T &b)
{
    cout<<"func1"<<endl;
    if(a == b)
    {
        return true;
    }
    return false;
}
​
// bool my_cmp(Person &a, Person &b)
// {
//     cout<<"func2"<<endl;
//     if(a.m_age == b.m_age)
//     {
//         return true;
//     }
//     return false;
// }
​
template<>bool my_cmp(Person &a, Person &b)
{
    cout<<"func3"<<endl;
    if(a.m_age == b.m_age)
    {
        return true;
    }
    return false;
}
​
int main(int argc, char const *argv[])
{
    /* code */
    Person p1(10);
    Person p2(20);
    cout<<my_cmp(p1,p2)<<endl;
    return 0;
}

3.4 类模板

作用:建立一个通用类,类中成员的数据类型可以不具体指定,用一个虚拟的类型来代表

语法:

template<typename T>
类

同样,typename可以用class代替

/*类模板*/
#include <iostream>
#include <string>
using namespace std;
​
template<typename NameType, typename AgeType>
class Person
{
public:
    Person(NameType name, AgeType age)
    {
        this->m_name = name;
        this->m_age = age;
    }
​
    void show()
    {
        cout<<"name: "<<this->m_name<<" age: "<<this->m_age<<endl;
    }
​
    NameType m_name;
    AgeType m_age;
};
​
int main(int argc, char const *argv[])
{
    /* code */
    Person<string, int> p1("henry",12);
    p1.show();
​
    Person<string, double> p2("henry",12.5);
    p2.show();
    return 0;
}

输出:

name: henry age: 12
name: henry age: 12.5

3.4.1 类模板与函数模板区别:

1)自动类型推导不适用于类模板(更不会发生重载)

继续上面的例子:

Person p3("henry",12.5);
p3.show();

报错:error: missing template arguments before 'p

2)类模板在模板参数列表中可以有默认参数

template<typename NameType, typename AgeType = int>
class Person
{
public:
    Person(NameType name, AgeType age)
    {
        this->m_name = name;
        this->m_age = age;
    }
​
    void show()
    {
        cout<<"name: "<<this->m_name<<" age: "<<this->m_age<<endl;
    }
​
    NameType m_name;
    AgeType m_age;
};
​
int main(int argc, char const *argv[])
{
    /* code */
    Person<string, int> p1("henry",12);
    p1.show();
​
    Person<string, double> p2("henry",12.5);
    p2.show();
​
    //参数列表可以有默认参数
    Person<string> p6("henry",100.5);
    p6.show();
​
    return 0;
}

输出:

name: henry age: 12
name: henry age: 12.5
name: henry age: 100

3.4.2 类模板中成员函数创建时机

类模板中成员函数创建时机和普通类中成员函数创建时机是有区别的

  • 普通类中:一开始就创建

  • 类模板中:成员函数调用时创建

class Person1
{
public:
    void showPerson1()
    {
        cout<<"Person1"<<endl;
    }
};
​
class Person2
{
public:
    void showPerson2()
    {
        cout<<"Person2"<<endl;
    }
};
​
template<typename T>
class MyClass
{
public:
    T obj;
    void func1()
    {
        obj.showPerson1();
    }
    void func2()
    {
        obj.showPerson2();
    }
};
​
int main()
{
    MyClass<Person1> m;
    m.func1();
    
    //m.func2();    //报错
}

3.4.3 类模板对象做函数参数

类模板实例化出的对象如何向函数传参

三种传入方式:

  • 指定传入的类型:直接显示对象的数据类型

  • 参数模板化:将对象中的参数变为模板进行传递

  • 整个类模板化:将这个对象模板化传递

#include <iostream>
using namespace std;
​
template<typename C1, typename C2>
class Person
{
public:
    Person(C1 name, C2 age)
    {
        this->name = name;
        this->age = age;
    }
    void showPerson()
    {
        cout<<"name: "<<this->name<<" age: "<<this->age<<endl;
    }
    C1 name;
    C2 age;
};
​
//1。指定数据类型
void print_person(Person<string, int>&p)
{
    p.showPerson();
}
​
//2.参数模板化
template<typename T1, typename T2>
void print_person2(Person<T1, T2>&p)
{
    p.showPerson();
​
    cout<<"T1的类型是: "<<typeid(T1).name()<<endl;
    cout<<"T2的类型是: "<<typeid(T2).name()<<endl;
}
​
//3.整个类模板化
template<typename T1>
void print_person3(T1 &p)
{
    p.showPerson();
​
    cout<<"T1的类型是: "<<typeid(T1).name()<<endl;
}
​
int main(int argc, char const *argv[])
{
    /* code */
    Person<string,int> p1("henry",24);
    print_person(p1);
    cout<<"-----"<<endl;
    print_person2(p1);
    cout<<"-----"<<endl;
    print_person3(p1);
    return 0;
}
​

3.4.4 类模板与继承
  • 子类继承的父类是一个类模板时,子类声明时要指定父类中T的类型。

  • 如果不指定,无法确定T的类型从而无法分配内存

  • 要想灵活指定父类的T的类型,子类也需要变成类模板

    #include <iostream>
    using namespace std;
    ​
    template<typename T>
    class Base
    {
    public:
        T name;
    };
    ​
    //class Child : public Base //报错
    //class Child : public Base<int>  //可以,但限制为int了
    ​
    template<typename T1, typename T2>
    class Child : public Base<T1>
    {
    public:
        Child()
        {
            cout<<"T1 类型:"<<typeid(T1).name()<<endl;
            cout<<"T2 类型:"<<typeid(T2).name()<<endl;
        }
        T2 age;
    };
    ​
    int main(int argc, char const *argv[])
    {
        Child<string,int> c;
        return 0;
    }

3.4.5 类模板成员函数类外实现

语法:

template<typename T1, typename T2>
void Person<T1, T2>::printPerson(){}

#include <iostream>
​
using namespace std;
​
template<typename T1, typename T2>
class Person
{
public:
    Person(T1 name, T2 age);
    void printPerson();
​
    T1 name;
    T2 age;
};
​
//构造函数类外实现
template<typename T1, typename T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
    this->name = name;
    this->age = age;
}
​
//成员函数类外实现
template<typename T1, typename T2>
void Person<T1, T2>::printPerson()
{
    cout<<"name is: "<<this->name<<" age is: "<<this->age<<endl;
}
​
int main(int argc, char const *argv[])
{
    /* code */
    Person<string,int> p("Tom",20);
    p.printPerson();
    return 0;
}

3.4.6 类模板分文件编写

由于类模板成员函数创建时机是在调用阶段,导致分文件编写时链接不到。

解决方法:

  • 直接include .cpp文件

  • 或者声明实现(.h和.cpp)写在一个文件里,后缀名为.hpp

3.4.7 类模板与友元
  • 全局函数类内实现:直接在类内声明友元即可

  • 全局函数类外实现(复杂):需要提前让编译器直到全局函数的存在

回顾:友元就是函数/另一个类可以访问当前类中的私有成员/成员函数

全局函数类内实现:

class Person
{
private:
    friend void printPerson(Person p)
    {
        cout<< p.name <<" "<<p.age <<endl;
    }
    
    string name;
    int age;
​
public:
    Person(string name, int age)
    {
        this->name = name;
        this->age = age;
    }
};
​
int main(int argc, char const *argv[])
{
    /* code */
    Person p("henry",24);
    printPerson(p);
    return 0;
}

同样可以应用到模板:

#include <iostream>
using namespace std;
​
template<typename T1, typename T2>
class Person
{
private:
    friend void printPerson(Person<T1,T2> p)
    {
        cout<< p.name <<" "<<p.age <<endl;
    }
    
    T1 name;
    T2 age;
​
public:
    Person(T1 name, T2 age)
    {
        this->name = name;
        this->age = age;
    }
    
};
​
int main(int argc, char const *argv[])
{
    /* code */
    Person<string, int> p("henry",24);
    printPerson(p);
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值