c++primer 复习笔记

auto变量自动根据表达式推断应该的类型(比如为 class 或 double)

//cbegin()中的c是 const 的意思, 
//it3 类型是 vector<int>::const_iterator
auto it3 = v3.cbegin();

 

decltype也具有自动推断类型的功能:

//使用func()的返回值的类型定义abc,而不是使用func()返回值初始化abc
decltype(func()) abc;
//使用表达式expr类型定义abc,而不是使用表达式初始化abc
decltype(expr) abc;

 

using 命名空间声明, 不应该在头文件中,因为可能导致意想不到的冲突

namespace std{
     //在此中定义的所有变量和函数,外部使用的时候都要声明,
     //可以 using std::cout; using std::cin; using std::endl;
     //或者 直接 using namespace std;
}

使用自定义的命名空间来防止命名空间污染.

 

使用 string 前,需要 #include<string> 并 using std::string;
string s1="abc"  等价于 string s1("abc"),不同在于前者调用的是string的拷贝构造函数("abc"会生成一个string类型的tmp),后者调用的是string的直接构造函数。

string比c风格字符串的优势在于,实现了运算符的重载,可以直接比较(<)或者操作(+)。
string与c风格字符串二者之间转换 string abc(char *cstring)<==>char *cstring = abc.c_str()

想对string中的每个字符做点什么的时候,可以 for(auto c : str) 

string str("hello world");
decltype(str.size()) cnt=0;    //定义cnt类型和str.size()返回值类型一样
for(auto c:str)    //这里c的类型其实就是char,但这样写明显更具有通用性
    if(isalpha(c))    //如果是字母的话
        cnt++;
for(auto &c:str)    //这里的c是引用,因此可以改写原值
    c = toupper(c);
#include <cstring>
#include <iostream>
#include <fstream>
#include <sstream>

//process "-ZX_HEADER" option:
//	Input: const char build_options[] = "-DLENGTH=12 -ZX_HEADER cl/abc.clT cl/def.clT -I include/";
//	Output: the content of "cl/abc.clT + cl/def.clT" files
//	NOTICE: it will remove  "-ZX_HEADER cl/abc.clT cl/def.clT" from s_ori !
std::string processZxHeader(std::string &s_ori)
{
	std::string tag_header = "-ZX_HEADER ";
	std::string tag_tail = ".clT";

	std::size_t pos_header = s_ori.find(tag_header);	//find position of "-ZX_HEADER "
	std::size_t pos_tail=0;
	std::string file_content;

	if(pos_header != s_ori.npos)
	{
		std::size_t pos_begin = pos_header + tag_header.length(); //position begin of file name
		std::size_t pos_end = 0;                                  //position end   of file name
		while((pos_tail=s_ori.find(tag_tail,pos_tail)) != s_ori.npos)	//find position of ".clT " from pos_tail position
		{
			pos_end = pos_tail + tag_tail.length();

			//get header file name string
			std::string target = s_ori.substr(pos_begin, pos_end-pos_begin);//-1: erase space of "clT".
			std::cout<<"target=\""<<target<<"\""<<std::endl;

			std::ifstream header(target.c_str());//header.open(target.data());
			if(header.is_open())
			{
				std::stringstream buffer;
				buffer << header.rdbuf();  //read file content in stringstream object
				file_content.append(buffer.str());
			}
			else 
			{
				std::cout<<"open "<<target<<" fail."<<std::endl;
				return "";
			}

			pos_begin = pos_end;	//position begin of next "cl/xxx.clT" (if have, SHOULD follow priv cl/xxx.clT)
			pos_tail = pos_begin;
		}
		s_ori.replace(pos_header,pos_end-pos_header,"");		//remove "-ZX_HEADER cl/abc.clT cl/def.clT" from s_ori
		return file_content;
	}
	return "";
}

使用vector前,需要 #include<vector> 并 using std::vector;
因为引用&使用的时候必须初始化,所以vector除了不可以是引用构成的vector外,其他绝大部分类型都可以

vector<string> v1={"hello","world"};    //列表方式初始化,拷贝构造
vector<string> v2=v1;    //拷贝构造
vector<string> v2(v1);    //直接构造
vector<int> v3;     //默认构造
for(int i=0;i<100;i++)
    v3.push_back(i);     //其他常用操作  v3.size()   v3.empty()    v3[i]     find(v3.begin(),v3.end(),12)!=v3.end()
for(auto &i : v3)
    i *= i;

vector<DType>::iterator 是 vector 类中定义的迭代器类型(所以 iterator 类的作用域在vector中), 
iterator 实际就是对指针进行了封装,指针操作的 " * " 和 " -> " 都可以正常使用. 
对 vector 的操作(如 push_back)会使迭代器失效(因为他们可能导致 vector 管理的那块内存的动态伸缩和释放)

 

重载函数(overloaded function)是指函数名与其他函数相同的函数,多个重载函数必须在形参数量或形参类型上有所区别

 

类的 public 成员定义类的接口,private 成员可被类的成员函数访问,但不可以被类外访问,用 private 封装实现细节。
类本身就是一个作用域,所以类外定义成员函数需要加上 :: 
c++ 中使用 struct/class 定义类的唯一区别在于默认访问权限

//类成员函数 
std::string isbn() const {  //const 表示本成员函数承诺不会通过 this 指针修改类中的成员(但是可以修改 mutable 成员 p271)
    return this->book_number;
}

classA& classA::func(const classA &rhs) {
    //这个函数返回 classA& 是因为他想模仿某个内置运算符,eg += , =
    //这些运算符会把左值当作赋值表达式的结果,所以这个函数返回运算结果的引用
    //abc.up().left();
    ...
    return *this;
}

还有一种返回 *this 的情景是想以这种方式调用(p273): classA.move(x,y).scale(r)
一定注意需要返回引用(否则将是副本)


构造函数控制类的初始化:名字与类相同,没有返回值
在我们没有定义任何构造函数时,合成的默认构造函数将被编译器定义,合成的默认构造函数只适用于非常简单的类

class classA{
    classA() = default;    //代表使用默认构造函数(c++11的新特性)

    //:后面的是构造函数初始值列表,
    //(初始值列表在初始化 const 数据成员 或 引用类型的数据成员 时很有用,
    //因为他们都只可以在定义的同时初始化,放在函数体内部就晚了 p285),
    //此外需注意初始值列表初始化顺序和 数据成员在类中的定义顺序 一样!(列表中的位置关系没有用..)
    classA(const std::string &s, unsigned int i): m_str(s),m_int(i){ }

    //构造函数前有 explicit 关键字则不允许隐式类型转换(传入类型就得是string)
    explicit classA(const std::string &s):classA(s,0){}    //c++11 新特性,委托构造,其实就是调用其它构造函数
    classA(unsigned int i):classA("",i){}                //c++11 新特性,委托构造,其实就是调用其它构造函数

    std::string m_str;    //在头文件中使用 using 不是好主意,所以这里用作用域
    unsigned int m_int;
};

对类使用赋值运算符 或 以值的方式传递 返回对象 时,会触发类的拷贝构造函数。
使用默认的拷贝构造可能会使类中malloc的资源出问题(类中资源全部以成员的方式存放在类中,才可以使用默认的拷贝构造)

 

类可以允许其他类或者函数访问其非public成员,方法是令其他类或者函数成为他的友元(friend)p267
友元的声明仅仅影响访问权限,普通的声明还是要声明的

class classA{
    friend std::istream &read(std::istream &is, classA &ca); //这样这个函数就可以访问 classA 的 private 成员了
    friend std::ostream &print(std::ostream &os, const classA &ca);
    friend class classB;    //这样 classB 的所有成员函数均可以访问 classA 的私有成员
    friend void classB::clear();    //这样只有classB的clear()成员函数拥有访问classA私有成员的权限
    //
    ...
};

 

类可以定义某种类型在类中的别名(typedef std::string::size_type my_type; 或者 using my_type = std::string::size_type; // ),需要注意的是此别名和其他成员一样存在 public/private 访问限制

 

定义在类内部的成员函数将被自动 inline,可以在类内部显式 inline 声明然后类外部定义,或者类外部定义显式写 inline

 

类的静态成员与类关联,也可以是 public/private 的,不属于对象,必须在类的外部定义和初始化静态成员(类似于全局变量,只可以被定义一次), 静态函数成员不包含this指针(即不和对象绑定一起,也不可以使用this)


对于拥有构造函数的对象而言 new ABC 与 new ABC() 效果等同,但对于内置类型而言,
eg. new int 指向的int未初始化,而 new int()指向的int采用值初始化(0)

 

动态内存管理3个常见问题:
1.忘记 delete
2.使用已经释放的内存,释放内存后将其置为 nullptr 是个好习惯,防止野指针,但是保护有限,比如可能存在多个指针指向相同的内存
3.重复delete(p437)
坚持只使用智能指针可以避免所有这些问题! 智能指针和内置指针最好不要混用(因为一旦智能指针接管了内置指针,就可能在某些时候释放他)!

智能指针可以确保即使异常发生(在释放资源的代码之前),资源也可以被正确释放(p441)
智能指针管理的类需要一个析构函数,且需要正确的在析构函数中释放资源,否则智能指针也帮不了你

使用智能指针需要 #include <memory>
智能指针类能记录有多少个用户通过 shared_ptr 指向了相同的对象,并在无 shared_ptr 指向对象时
(智能指针使用 reset() 或 =nullptr 主动放弃 或 智能指针本身被销毁),将对象释放
(机制很像 garbage collect,看起来是内置指针wrap了一层,大家都使用wrapped管理结构access内置指针指向的对象,
wrapped管理结构每个用户分配一个,拷贝或赋值的时候会先更新wrapped管理结构,然后分裂出一个新的wrapped管理结构传出去)

shared_ptr<double> p1;    //将被初始化为空指针
//p2指向一个值为1024的int(或者说,p2接管了内置指针),
//用来构造智能指针的内置指针必须指向动态内存,因为只能指针指不定什么时候就用delete将其释放啦
shared_ptr<int> p2(new int(1024));
//错误的! 因为c++没有提供内置指针到智能指针的隐式转换
shared_ptr<int> p3 = new int(1024);

//caffe 中智能指针使用例子:
vector<shared_ptr<Layer<Dtype> > > layers_;
for (int layer_id = 0; layer_id < param.layer_size(); ++layer_id) {
    layers_.push_back(LayerRegistry<Dtype>::CreateLayer(layer_param));
}
shared_ptr<SyncedMemory> diff_;    //梯度信息
diff_.reset(new SyncedMemory(capacity_ * sizeof(Dtype)));    //reset()可能会触发智能指针 原指向内存 的释放操作

//当想通过一个函数返回一个shared_ptr时,可以使用make_shared
shared_ptr <classA> p = std::make_shared<classA>(xxxx,xxx,xxx);//(xxxx,xxx,xxx)是classA构造函数所需参数

智能指针调用 get() 将返回其管理的内置指针,但在拿到内置指针后:
1.不准使用delete释放内置指针(这是智能指针干的活)
2.不准用内置指针再去构造另一个智能指针 (两个智能指针管理一个内置指针
  会混乱的!我怎么知道另一个智能指针是否已经将动态内存给释放了呢??)

p438 表12.3很有用
p443 智能指针注意事项总结
https://www.cnblogs.com/diysoul/p/5930396.html
https://www.2cto.com/kf/201612/580580.html
https://www.cnblogs.com/lsgxeva/p/7788061.html

如果限制 shared_ptr 不可以进行普通的拷贝和赋值,那就是 unique_ptr ,unique_ptr 只允许某个用户独占,可以转移给其他用户但是不可以俩用户共享

weak_ptr 指向一个 shared_ptr 管理的对象,但是不会触发 shared_ptr 更改引用计数,即 一旦最后一个指向对象的 shared_ptr 被销毁,对象就被释放, 即使还有 weak_ptr 指向这个对象

std::unique_ptr<int> up1(new int(11));   // 无法复制的unique_ptr
//unique_ptr<int> up2 = up1;    // err, 不能通过编译
std::unique_ptr<int> up3 = std::move(up1);    // 现在up3是数据的唯一的unique_ptr
//std::cout << *up1 << std::endl;   // err, 运行时错误,因为 up1 已经不再接管了
up3.reset();            // up3主动放弃管理那个内置指针
up1.reset();            // 不会导致运行时错误(up1 本来就不管理谁)
//std::cout << *up3 << std::endl;   // err, 运行时错误
std::unique_ptr<int> up4(new int(22));
up4.reset(new int(44)); //"绑定"动态对象
std::cout << *up4 << std::endl; // 44
up4 = nullptr;//显式销毁所指对象,同时智能指针变为空指针。与up4.reset()等价
std::unique_ptr<int> up5(new int(55));
int *p = up5.release(); //只是释放控制权,不会释放内存.意思是up5不再接管这块内存
std::cout << *p << std::endl;
//cout << *up5 << endl; // err, 运行时错误
delete p; //释放堆区资源
void check(std::weak_ptr<int> &wp){
    std::shared_ptr<int> sp = wp.lock(); // 转换为shared_ptr<int>
    if (sp != nullptr){
        std::cout << "still: " << *sp << std::endl;
    } else {
        std::cout << "still: " << "pointer is invalid" << std::endl;
    }
}

std::shared_ptr<int> sp1(new int(22));    //智能指针接管内置指针
std::shared_ptr<int> sp2 = sp1;    //通过智能指针access将触发引用计数改变
std::weak_ptr<int> wp = sp1; //指向shared_ptr<int>所指对象,虽然access,但不会触发引用计数改变

std::cout << "count: " << wp.use_count() << std::endl; // count: 2
std::cout << *sp1 << std::endl; // 22
std::cout << *sp2 << std::endl; // 22
check(wp); // still: 22

sp1.reset();    //sp1 主动放弃管理那个内置指针
std::cout << "count: " << wp.use_count() << std::endl; // count: 1
std::cout << *sp2 << std::endl; // 22
check(wp); // still: 22

sp2.reset();    //sp2 主动放弃管理那个内置指针
std::cout << "count: " << wp.use_count() << std::endl; // count: 0
check(wp); // still: pointer is invalid


重载的运算符是具有特殊名字的函数:由关键字 operator 和 其后的运算符号 组成
若一个运算符函数是类成员函数,则他的第一个运算对象绑定到隐式的 this 指针

一个运算符函数至少要包含一个类类型的参数
int operator+ (int,int);    //error! 不可以为 int 重定义内置的运算符!

运算符函数应该与内置类型含义一致: 
如果类执行io操作,则定义移位运算符使其与内置类型的io保持一致
如果类的某个操作是检查相等性,则定义 operator== (and operator!=)
如果类包含一个内在的单序比较操作,则定义 operator< (and 其他一些关系操作)

重载运算符返回类型通常应与内置版本返回类型兼容: 关系运算返回bool,算术运算符
返回类类型,赋值和复合赋值返回左侧运算对象的一个引用

重载的运算符函数可以选择作为成员函数或非成员函数,但是:
1.赋值(=)下标([])调用(())成员访问(->)运算符必须是成员函数
2.复合赋值(+=,-=,*=,/=)不同于赋值(=),最好是成员函数,但不强制
3.改变对象状态或与对象密切相关的,通常为成员(如 operator++ operator-- 解引用*)
4.具有对称性的运算符 如 算数(加减乘除) 相等性 关系 和 位运算,通常应该是非成员函数.(成员函数的话相当于第一个参数是this,当然class+xx,等价于class.operator+(xx),没有问题,但是xx+class可能就出事了)

//重载输出运算符函数 operator<< 和 输入运算符函数
//因为可能 access 类 classABC 中的私有成员,需要将函数声明为 classABC 的友元
//输入输出运算符函数应该是非成员函数,否则调用就必须要 xxx<<cout 了
ostream &operator<<(ostream &os, const classABC &xxx){    //注意第一个参数是 os 而不是 this
    os<<xxx.funcA()<<" "<<xxx.mA<<xxx.mB;    //尽量减少格式化操作,so这里最好不要加 endl
    return os;    //返回对 os 的引用
}

istream &operator>>(istream &is, classABC &xxx){
    is>>xxx.mA>>xxx.mB;
    if(is){
        //
    } else {    //输入失败
        xxx.mA = 0;
    }
    return is;    //返回对 is 的引用
}

//因为 + 是对称的操作,所以可能会有 class+int 这种case,还是作为非成员函数更好
classABC operator+(const classABC &lhs, const classABC &rhs){
    classABC sum = lhs;    //赋值构造函数
    sum += rhs;    //调用这个类的(重载的)复合赋值运算函数
    return sum;
}

//operator< 与下面是类似的,
//friend函数应该出现在 classABC定义的前面,还需要加classABC的前向声明
bool operator==(const classABC &lhs, const classABC &rhs){
    return (lhs.mA==rhs.mA) && (lhs.mB==rhs.mB);
}

bool operator!=(const classABC &lhs, const classABC &rhs){
    return !(lhs == rhs);    //调用这个类的(重载的)operator==
}

//赋值和复合赋值都是要返回对this的引用的
classABC& classABC::operator+=(const classABC &rhs){
    mA += rhs.mA;
    mB += rhs.mB;
    return *this;
}

//以后这个类就可以这样赋值了classABC xxx={a,b,...};
classABC& classABC::operator=(initializer_list<int> ii){
    vec_ii(ii);    //vector<int> vec_ii;
    return *this;
}

classABC& classABC::operator++(){    //前置运算
    //check();
    mA++;
    mB++;
    return *this;
}
classABC& classABC::operator++(int){    //后置运算,为了区分,接收一个不被使用的int形参
    classABC ret = *this;    //记录当前值
    ++*this;    //调用前置++运算
    return ret;
}

class classABC{
    //这样这个函数就可以访问 classA 的 private 成员了,
    //这些friend函数的定义还需要在class定义的前面,不然编译器会找不到其定义
    friend ostream &operator<<(ostream &os, const classABC &xxx);
    friend istream &operator>>(istream &is, classABC &xxx);
    friend classABC operator+(const classABC &lhs, const classABC &rhs);
    //如果是template的话,  ... operator==<T>()...
    friend bool operator==(const classABC &lhs, const classABC &rhs);
    friend bool operator!=(const classABC &lhs, const classABC &rhs);
    //
public:
    classABC &operator=(initializer_list<int>);
    classABC &operator+=(const classABC &rhs);

    //operator[]可作为左值或右值,所以一个const版本一个非const版本
    string & operator[](size_t n){
        return elements[n];
    }
    const & operator[](size_t n) const{    //这个返回常引用 且承诺不会通过this修改类成员
        return elements[n];
    }
    classABC& operator++();    //前置运算,要返回对this的引用
    classABC& operator--();    //前置运算,要返回对this的引用
    classABC& operator++(int);    //后置运算,为了区分,接收一个不被使用的int形参
    classABC& operator--(int);    //后置运算,为了区分,接收一个不被使用的int形参
    ...
private:
    string *elements;    //elements 指向 string 组成的数组
    int mA,mB;
    int cur;
};
//重载了函数调用运算符的话,就可以向使用函数一样使用该类的对象
//lambda 是函数对象,标准库就定义了一系列这样的类,eg. plus 类 greater 类
//sort(svec.begin(),svec.end(),greater<string>())    //greater 类
//sort(svec.begin(),svec.end(),[](string &a,string &b){return a<b;})    //lambda
//重载了函数调用运算符的类可以作为泛型算法实参传入,for_each(vs,begin(),vs.end(),PrintString(cerr,'\n'))
class PrintString{
public:
    PrintString(ostream &o=cout, char c=' '):os(o),sep(c){}
    void operator()(const string &s) const{
        os<<s<<sep;
    }
private:
    ostream os;
    char sep;    //将不同输出隔开的字符
}

PrintString printer;
printer(s);
PrintString error(cerr);
error(s);

c++中的可调用对象包括 函数 函数指针 lambda表达式 bind创建的对象 和 重载了函数调用运算符的类

int add(int i, int j){return (i+j);}    //普通函数
auto mod = [](int i, int j){return (i%j);}    //lambda
struct divide{    //函数对象类
    int operator()(int i, int j){
        return i/j;
    }
}

map<string, int(*)(int,int)> binops;
binops.insert({"+",add});    //ok,{a,b}是一个pair
binops.insert({"%",mod});    //error, lambda 类型不同于函数指针!

//可以使用 function 类型统一可调用类型(需 #include <functional>)!
function<int(int,int)> f1 = add;    //函数指针
function<int(int,int)> f2 = [](int i, int j){return (i%j);}    //lambda
function<int(int,int)> f3 = divide();    //函数类的对象
map<string, function<int(int,int)> > binops={
    {"+",add},    //函数指针
    {"-",std::minus<int>()},    //标准库函数对象
    {"*",[](int i, int j){ return i*j;}},    //匿名 lambda
    {"/",divide()},    //用户自定义的函数对象
    {"%",mod}    //命名了的 lambda
};

为类定义 类型转换 运算符,

class SmallInt{
public:
    SmallInt(int i=0):val(i){
        if(i<0 || i>255)
            throw std::out_of_range("bad value");
    }
    //这样就支持将此类(SmallInt)转换为 int,
    //注意这个函数没有返回类型(就是int嘛),且参数列表必须是空
    //隐式的类型转换可能带来意想不到的问题,所以可以
    //explicit operator int() const{..} //explicit保证不允许隐士的类型转换
    operator int() const{
        return val;
    }
private:
    std::size_t val;
}


面向对象程序设计(OOP)核心思想是数据抽象,继承和动态绑定
通过数据抽象,将类的接口和实现分离
使用继承可以定义相似的类型并对其相似性关系建模
使用动态绑定可在一定程度上忽略相似类型的区别,以统一的方式使用它们的对象

class classA{
public:
    classA() = default;    //使用默认构造函数(c++11的新特性)
    classA(const std::string &s, double d):
        str(s),data(d){ }

    //类中的某些函数,基类希望他的派生类各自定义适合自身的版本,
    //此时基类就将这些函数声明成 虚函数
    //virtual 只可以出现在类内部的声明语句之前(不可以放在类外部的函数定义)
    virtual double func(std::size_t) const;

    //作为继承关系中根节点的类通常会定义一个虚的析构函数
    virtual ~classA() = default;
protected:    //派生家族都可以自由访问 protected 成员
    double data = 0.0;
private:    //派生类想访问私有成员也是需要通过公共接口的
    std::string str;
}

//public 继承,则基类接口也是派生类接口的组成部分
class classB: public classA{    //类派生列表,
public:
    //派生类使用基类的构造函数来初始化基类部分
    classB(const std::string &s, double d, int i):
        classA(s,d), id(i) { }

    //派生类必须在内部对所有重新定义的虚函数进行声明,virtual 可不加,
    //(因为基类中该函数是虚函数,派生类中该函数隐式也是虚函数)
    //override 是c++11新特性,显式注明此函数将改写基类的虚函数,这样编译器可以帮忙检查
    //派生类可以选择不重写虚函数(此时虚函数就和普通函数一样)
    double func(std::size_t) const override;
private:
    int id;
}

通过 classA 的指针或引用,可以统一调用 classA/classB 对象(访问派生类对象中的基类部分),动态绑定保证通过指针或引用调用虚函数时,根据真实所指向的对象不同,可能执行基类的版本或派生类的版本

若基类定义了一个静态成员,则在整个继承体系中只存在该成员的唯一定义。该静态成员可以通过类(基类或派生类)访问,也能通过对象(基类对象或派生类对象)访问

派生类中显式调用基类中的函数,可以直接 classA::func()

若是一个类不希望被其他类继承,可以在定义的时候 加上 final, eg:

//classB 继承自classA,但classB不希望被其他类继承了
class classB final: public classA{
    //final 还可以用在类中的某个函数上,
    //这样派生类就不可以覆盖它(与虚函数相对)
    void f1(int) const final;
}

含有纯虚函数的类是抽象基类,抽象基类只负责定义接口, 不可以创建抽象基类的对象

多重继承时,派生类构造函数只初始化他的直接基类

派生类的作用域在基类作用域之内(所以当一个名字无法在派生类作用域内正确解析, 编译器将继续在外层的基类作用域中寻找名字的定义)

(15.7 - 尚未看)

 

一个模板就是一个创建 类 或 函数 的蓝图或公式. 当使用一个 vector 这样的泛型类型 或 find 这样的泛型函数时,我们提供足够的信息,将蓝图 转换为特定的类或函数

//模板定义以关键字 template 开始,后跟模板参数列表(逗号分开)

template <typename T>    //类型参数前必须加 class 或 typename,(class 和 typename 等价)
int compare(const T &v1, const T &v2){    //const 引用保证函数可用于不能拷贝的类型
    //只使用<运算符,so参与运算的类(实参)不要求一定实现>操作
    //这降低了对类的要求
    if(v1 < v2) return -1;
    if(v2 < v1) return 1;//if(v1 > v2) return 1;
    //下面这种写法更好,因为他也可以用于指针的比较
    //if(less<T>(v1,v2)) return -1;
    //if(less<T>(v2,v1)) return 1;
    return 0;
}

//使用模板时,(隐式或显式)指定模板实参,将其绑定到模板参数上
//编译器用函数实参推断模板实参,然后为我们实例化一个特定版本的函数
cout<<compare(1,0)<<endl;    //T 为 int

 

除了定义类型参数,还可以定义非类型参数(用来表示一个值而非一个类型) 当被实例化时,非类型参数被用户提供的或编译器推断的值(必须是常量 表达式)代替

template <typename T> class classA{
public:
    //类内部俩typedef,作用域也只在类内
    typedef T value_type;
    typedef typename std::vector<T>::size_type size_type;

    //定义在类模板之内的成员函数的模板参数与类模板相同,
    //所以不必加template
    void push_back(const T &t){data->push_back(t)};
    void pop_back();
    T& operator[](size_type i);
private:
    std::shared_ptr<std::vector<T>> data;    //指向数据vector的指针
    //内部检查 index 的函数,看起来还挺通用的
    void check(size_type i, const std::string &msg) const;
}

//定义在类外部的成员函数需要添加 typename
//(因为每个实例化的类都有一份对应的)
//定义了模板类后,这些模板类的成员函数也需要在头文件中定义了,不然编译器会找不到
template <typename T>
void classA<T>::check(size_type i, const std::string &msg) const{
    if(i>=data->size())
        throw std::out_of_range(msg);
}

template <typename T>
T& classA<T>::operator[](size_type i){
    check(i,"subscript out of range");    //
    return (*data)[i];    //返回 T&,如果 T 是 string 或 vector,那就有用了
}
template <typename T>
void classA<T>::pop_back(){
    check(0,"pop back on empty size");    //(data->size()<=0)的时候
    data->pop_back();
}




//类模板与函数模板不同之处在于编译器不能为类模板推断模板参数类型, so需要在定义的时候通过<>指定
//类模板用来实例化一个类型,本身并不是类型名, classA<int> 才是类型名
classA<int> ia;

//此时编译器会实例化一个这样的类
template <> class classA<int> {
    //
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值