来自新世界:输入,输出和文件

金字塔输出hello

int main(){
  using namespace std;
  const char *p = "hello";
  int len = strlen(p);
  for (int i = ; i <= len;i++){
    cout.write(p, i);
    cout << endl;
  }
}

设置空格(只对下一个有效)

int main(){
  using namespace std;
  cout << "#";
  cout.width(12);
  cout << 12 << "#";
}

设置填充字符

int main(){
  using namespace std;
  //cout << "#";
  cout.fill('*');
  cout.width(12);
  cout << 12 << endl;
  cout.width(12);
  cout << 50 << endl;
}

显示全部数字

int main(){
  using namespace std;
  float p = 20.140;
  float p2 = 1.9 + 1.0 / 3.0;
  cout.setf(ios_base::showpoint);
  cout << p << endl;
  cout << p2 << endl;
}

 

来自新世界:标准模板库

什么是STL?

STL可分为容器(containers)、迭代器(iterators)、空间配置器(allocator)、配接器(adapters)、算法(algorithms)、仿函数(functors)六个部分

 

引入:模板类vector

vector<int> ratings(2);

这里建立了一个类型为int,名字是rating的vector类,这个rating里面有两个int元素(其实就是数组)

但是这个vector类对外有接口,比如begin()方法返一个指向容器中第一个元素的迭代器,迭代器是什么?它可以是指针,可对其执行类似指针的操作(解除引用,递增),它存在的意义就是让STL能够为各种各样不同的容器类提供统一的接口。迭代器的类型是一个名字为iterator的typedef,作用域为整个类。

int main(){
  vector<int>::iterator pd;
  vector<double> scores;
  pd = scores.begin();
  *pd = 55;
  pd++;

  auto pd = scores.begin();  //也可以通过auto直接声明使用
}

还有其他用法:

  vector<double> scores;
  double temp;
  while(cin >> temp){
    scores.push_back(temp);
  }
  cout << scores.size() << endl;
  scores.erase(scores.begin(), scores.begin() + 2);//删除第一个到第二个的元素
  scores.erase(scores.begin(), scores.end())       //end()返回末尾元素的后一个位置
  scores<int> new;
  scores.insert(scores.begin(), new.begin(), new.begin() + 5);

下面就来讲讲喜闻乐见的sort()函数,以前特别讨厌去写排序,一直想着去用sort去解决问题,那时都是用sort去对一个int/double数组进行排序,然鹅怎么对对象进行排序呢?

就是重载<,或者重新定义一个函数然后把函数当成参数传进sort()里面去

  bool operator<(const A &a1,const A &a2){
    if(a1.data > a2.data){
      return true;
    }
    else
      return false;
  }

  sort(book.begin(), book.end());

  bool Another(const A &a1,const A &a2){
    if(a1.data < a2.data){
      return true;
    }
    else
    {
      return false;
    }

    sort(book.begin(), book.end(), Another);

泛型编程

1.迭代器:连接容器和算法的一种重要桥梁,迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。迭代器修改了常规指针的接口,所谓迭代器是一种概念上的抽象:那些行为上像迭代器的东西都可以叫做迭代器。然而迭代器有很多不同的能力,它可以把抽象容器和通用算法有机的统一起来。

迭代器可以分为一下5种:

  • 输入迭代器:仅仅读。支持++、==、!=
  • 输出迭代器:仅仅写,支持++
  • 前向迭代器:读写,支持++、==、!=
  • 双向迭代器:读写,支持++、–
  • 随机訪问迭代器:读写,支持++、–、[n]、-n、<、<=、>、>=

 

前文提到像begin()方法,find()方法它也会返回一个迭代器,我们来看一下它的函数原型:

template <class InputIterator, class T>
InputIterator find(InputIterator first, InputIterator last, const T &value);

 

 

这里声明了find方法要传进一个输入迭代器,

我们可以看出,find是一种算法,vector是一种容器里面可以塞任何东西,当需要把算法应用于容器时,我们为了控制,才使用迭代器这个东西,可以看出不同的迭代器有着不同的权限,这也要求我们要根据算法的需求来选择迭代器。比如查找算法只需要定义++运算符和读取数据(假如查找算法可以修改数据将会变得很蛋疼)

 

sort算法使用双向迭代器对vector里面的数据进行修改与排序

 

 

事实上,迭代器是广义上的指针,指向int的常规指针是一个随机访问迭代器模型。

  ostream_iterator<int, char> out_iter(cout, "\n");  //第一个参数是输出方式,第二个是分隔符
  int x[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  vector<int> dice(10);  
  copy(x, x + 10, dice.begin());   //复制函数
  copy(dice.begin(), dice.end(), out_iter);    //将dice容器中的内容复制到输入流中
  exit(5);

再来讲讲其他有用的迭代器

1.反向类迭代器,vector里面有一个rbegin和rend方法,返回的值分别和end与begin方法返回的值相同,但是前两者返回的迭代器的类型是reverse_iterator而不是iterator

2.插入迭代器(预定义的),在通过copy来把数据插到容器中时,很有可能会覆盖掉前面的数据,那么这时就要把特殊的迭代器传进容器中了

copy(dice.begin(), dice.end(), back_insert_iterator< vector<int> > (words)); //将dice里面的内容从后面复制到words中

 

顺带一提copy可以:

  1. 将信息从一个容器复制到另外一个容器中
  2. 把数据从容器中复制到输出流
  3. 把信息从一个容器中插入到另外一个容器中

我们再来回顾一下前面的那一张图,我们可以发现,copy作为一种独立的算法,通过传给它不同的迭代器,可以实现对不同种类的容器的不同操作(输出,复制,插入)。

 

2.容器

list vector 什么的,还有关联容器set map pair,看的脑子疼就不计下来了

那个map和pair可以搭配使用

// multmap.cpp -- use a multimap
#include <iostream>
#include <string>
#include <map>
#include <algorithm>

typedef int KeyType;
typedef std::pair<const KeyType, std::string> Pair;
typedef std::multimap<KeyType, std::string> MapCode;

int main()
{
    using namespace std;
    MapCode codes;

    codes.insert(Pair(415, "San Francisco"));
    codes.insert(Pair(510, "Oakland"));
    codes.insert(Pair(718, "Brooklyn"));
    codes.insert(Pair(718, "Staten Island"));
    codes.insert(Pair(415, "San Rafael"));
    codes.insert(Pair(510, "Berkeley"));

    cout << "Number of cities with area code 415: "
         << codes.count(415) << endl;
    cout << "Number of cities with area code 718: "
         << codes.count(718) << endl;
    cout << "Number of cities with area code 510: "
         << codes.count(510) << endl;
    cout << "Area Code   City\n";
    MapCode::iterator it;
    for (it = codes.begin(); it != codes.end(); ++it)
        cout << "    " << (*it).first << "     "
            << (*it).second    << endl;

    pair<MapCode::iterator, MapCode::iterator> 
		auto range
         = codes.equal_range(718);
    cout << "Cities with area code 718:\n";
    for (it = range.first; it != range.second; ++it)
        cout <<  (*it).second    << endl;
    // cin.get();
    return 0; 
}

我感觉有点用的核心语句:

//利用map可以像数组那样子通过[]来访问数据来统计单词
map<string, int> wordmap;
set<string>::iterator si;
for (si = word.begin(); si != word.end();si++)  //si指向单词
     wordmap[*si] = cout(word.begin(), word.end(), *si);

 

函数对象

函数对象是可以以函数方式与()结合使用的任意对象,包括:

1)函数名;

2)指向函数的指针;

3)重载了()操作符的类对象(即定义了函数operator()的类)

那又何必有所谓的仿函数呢?原因在于函数指针毕竟不能满足STL对抽象性的要求,也不能满足软件积木的要求——函数指针无法和STL其他组件(如配接器adapter)搭配,产生更灵活的变化。同时,函数指针无法保存信息,而仿函数可以。

 

。。。

 

看不懂,可能是直接用函数对象比较方便吧,比如:
template <class T,class S>   
  void fun(T &a,S &b){
    cout << a << ' ' << b << endl;
  }
//template void fun<int, double>(int &a, double &b);
int main(){
  
  int a = 1;
  double b = 2.5;
  string c = "hello";

  fun(a, b);
  void (*p1)(int &a, string &c) = fun;    //函数指针版本
  p1(a, c);

}
void show(const int a){
  cout << a << endl;
}
int main(){
  ostream_iterator<int, char> out_iter(cout, "\n");  //第一个参数是输出方式,第二个是分隔符
  int x[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  vector<int> dice(10);  
  copy(x, x + 10, dice.begin());   //复制函数
  for_each(dice.begin(), dice.end(), show);    //直接传函数版本
}
template <class T>
class show{
  public:
    show(){}
    void operator()(T &i) { cout << i << endl; }
};
int main(){
  ostream_iterator<int, char> out_iter(cout, "\n");  //第一个参数是输出方式,第二个是分隔符
  int x[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  vector<int> dice(10);  
  copy(x, x + 10, dice.begin());   //复制函数
  for_each(dice.begin(), dice.end(), show<int>());  //使用函数对象
}

 

这里可以发现,要用函数指针,先要这样子写一大串东西,然后才能传进去,但是用类的话会方便一点,直接实例化A<int>/<double>/<string>就完事了,然后还可以保存信息什么的,扩展性很高,但是占用内存更大和效率会比函数指针要慢得多。

 

其他库

1.vector,valarray,array

2.模板initializer_list:就是可以使用初始化列表语法来初始化STL容器,因为容器类现在包含将initializer_list<T>作为参数的构造函数。例如vector<double>包含一个将initializer_list<double>

作为参数的构造函数。

vector<int> x {1, 2, 3, 4, 5};

数据可以进行有必要的转化但不能隐式的窄化转化

 

总结

STL是一个容器类模板,迭代器模板,函数对象模板和算法模板的集合,他们的设计都是基于泛编程原则的。

  • 诸如vector和set等容器类是容器的概念(容器,序列,关联容器)的模型
  • 有一些算法被表示为容器的类方法,但是大量算法都被表示为通用的,非成员函数的,这是通过将迭代器作为容器和算法之间的接口得以实现的(把迭代器(这个类)给传进去,比如要把数据复制到输出流就传一个ostream_iterator,这样就把数据复制到输出流中。比如只是单纯的复制的话就传一个输入迭代器就成)。还有一个优点就是,我们只需要一个诸如copy(),for_each()这样的函数就可以了而不需要为每一个容器提供不同的版本。另外一个优点就是STL算法可以用于非STL容器,如常规数组,string对象。
  • 上面可以看到,算法对容器是有要求的,容器必须支持算法所需的迭代器,不然就要提供一个专门的方法。
  • STL还提供了函数对象,函数对象是重载了()运算符的类,可以用函数的表示方法来调用这种类的对象,同时可以携带更多的信息。

来自新世界:智能指针

不会发生内存泄漏的东西

智能指针的三个模板:auto_ptr  unique_ptr shared_ptr

当智能指针过期时,其析构函数会自动调用delete来释放内存。

void remodel(std::string& str){
  std::auto_ptr<std::string> ps(new std::string(str));
  *ps = str;
  cout << *ps;
  return;
}

注意所有指针都有一个explicit的构造函数,因此不能这样做:

shared_ptr<double> pd;
double *p_reg = new double;
//pd = p_reg;   不允许,需要显示转换
pd = shared_ptr<double>(p_reg);

事实上,我们可以发现,在编译文件时,vs code会发出警告,那是因为C++11已经要抛弃这个auto_ptr了。

为啥呢?

这个是因为有一个东西叫所有权,当一个智能指针赋值给了另外一个时,原来的就会放弃它的所有权,因此再次用原来的智能指针访问数据时就会出错。(我们称为“悬挂指针”)

void remodel(std::string& str){
  std::auto_ptr<std::string> ps(new std::string(str));
  *ps = str;
  auto_ptr<string> pt = ps;
  cout << *pt;
  cout << *ps;   //这时系统会卡住
  return;  
}

怎么办?

可以把auto_ptr换成shared_ptr或者unique_ptr,前者的机制是当有多个指针指向同一对象时,引用计数增加,在析构时(先析构)那个最后声明的指针,其析构函数把计数器数减一,直到计数器降低到0时才会释放分配的空间。后者的机制是在使用等于号那一步编译器就报错了。但其实也不是不能通过等于号来给unique_ptr指针赋值,如果给它赋值的是临时右值,就不会报错,因为反正这个右值它很快就会被销毁了。

unique_ptr<string> demo(const char*s){
  unique_ptr<string> temp(new string(s));
  return temp;
}

unique_ptr<string> ps;
ps = demo("Uniquely special");   //编译器不会报错

事实上也可以单纯的用等号,右值也不用是临时变量,在要转移的指针上加个move方法就行

怎么选择智能指针

如果有多个指针指向同一个元素,比如有很多个对象,但是每个对象里面都有两个指针,指向某个最大值和最小值,这时就要用shared_ptr

来自新世纪:打开文件

#include <iostream>
#include <string.h>
#include <fstream>
using namespace std;
int main(){
  ifstream fin;
  fin.open("a.txt");
  if(fin.is_open()==false){
    cerr << "can't open file\n";
    exit(EXIT_FAILURE);
  }
  string item;
  int count = 0;
  getline(fin, item, ':');
  while(fin){
    ++count;
    cout << count << ":" << item << endl;
    getline(fin, item, ':');
  }
  cout << "Done" << endl;
  fin.close();
  return 0;
}

 

来自新世界:RTTI和类型转换运算符

RTTI元素

  • dynamic_cast 运算符将使用一个指向基类的指针来生成一个指向派生类的指针。否则返回一个空指针。
  • typeid 运算符返回一个指出对象的类型的值
  • type_info 结构储存了有关特定类型的信息

上代码:

#include <iostream>
using namespace std;
class A{
    private:
      int i;
    public:
    A(int j):i(j){}
    virtual void show(){
        cout << "this is A:" << i << endl;
    }
};
class B:public A{
    private:
      int k;
    public:
    B(int o,int p):A(o),k(p){}
    void show(){
        cout << "this is B:" << k << endl;
    }
    void hello(){
        cout << "fuck" << endl;
    }

};
int main(){
    A *p = new B(5,1000);
    B *pai = new B(100,66);
    /*if(p = dynamic_cast<B*>(pai))  //利用派生类指针生成派生类指针赋给基类指针
    {
        p->show();
        cout << "successful" << endl;
    }*/
    if(pai = dynamic_cast<B*>(p))  //利用基类指针生成派生类指针赋给派生类类指针
    {
        pai->show();
        cout << "successful" << endl;
    }
    
}

结果是:

 

dynamic_cast在将父类cast到子类时,父类必须要有虚函数!!

具体有什么用呢?

其实就是基类指针可以指向它的派生类,但是有时候通过基类指针会调用到基类没用的方法,这时我们就可以用这个转换去通过使用一个基类指针来生成一个派生类指针再赋给其他指针,从而进行灵活的运用而不是通过typeid 和 if else 来判断这个基类指针指向的到底是什么类,再把操作写进去

类型转换运算符

上面说的dynamic_cast就是一种,而其他的还有像const_cast可以通过指针改变被const指针指向的值:

High bar;
const High *p_bar = &bar;
High *pb const_cast<High *>(p_bar); //这样使得 pb 可以修改 bar 指向的对象

但是如果对象本来就是const,那终究是修改不了的。

来自新世界:关于子类父类的同名函数

之前我一直以为若子类的公有函数要和父类同名只能加个virtual,事实上不是的:

#include <iostream>
using namespace std;
class A{
    private:
      int i;
    public:
    A(int j):i(j){}
    void show(){
        cout << "this is A:" << i << endl;
    }
};
class B:public A{
    private:
      int k;
    public:
    B(int o,int p):A(o),k(p){}
    void show(){
        cout << "this is B:" << k << endl;
    }
    void hello(){
        cout << "fuck" << endl;
    }

};
int main(){
    A y(5);
    B x(5,5); 
    A *p = &x;   //派生类指针不能指向基类
    //y.show();
    //x.A::show();
    p->show();   //加了virtual就调用派生类(B)的show函数
    ((B *)p)->show();
}

 

 

这时我们可以发现,即使基类指针指向派生类,调用的依旧是父类的函数,而非像加了virtual那样子的调用子类版本的show(),但是当指针被强制转换为子类指针时才可以调用子类的函数。想要用基类指针调用不同的版本的函数,就要在父类的show()前加一个virtual。

然鹅我突然想到,为什么这两个show函数不会冲突呢?其实这两个函数不在同一个作用域里面,C++会先去子类中搜索要用的函数,找不到再去父类里面找,因此如果要在子类对象里面调用父类函数,可以在子类对象中使用解析符 :: 去调用基类方法。

int main(){
    A y(5);
    B x(5,5); //派生类指针不能指向基类
    A *p = &x;
    y.show();
    x.A::show();
}

最后来个汇总:

#include <iostream>
using namespace std;
class A{
    private:
      int i;
    public:
    A(int j):i(j){}
    void show(){
        cout << "this is A:" << i << endl;
    }
};
class B:public A{
    private:
      int k;
    public:
    B(int o,int p):A(o),k(p){}
    void show(){
        cout << "this is B:" << k << endl;
    }
    void hello(){
        cout << "fuck" << endl;
    }

};
int main(){
    A x(5);
    B y(5,5);
    //B *pt = &x;   派生类指针不能指向基类
    A *pi = &x;
    B *p = &y;
    pi->show();
    ((B *)pi)->show();   //将基类指针强制转换为派生类指针
    ((B *)pi)->hello();
    //p = &x;      报错,派生类不可以指向基类
    p->hello();
    p->show();
    ((A *)p)->show();       //将派生类指针强制转换为基类指针
    //y.show();
    //x.A::show();
}

结果:

 

 

(第二个由于没有默认构造一个B类,也就是没用构造好B类的部分,因此B类的私有成员值会变成一串无意义的数字)

来自新世界:友元,异常及其他

一个蛋疼的问题

那就是在写友元函数时会出现循环嵌套的情况,这时,就要在那个死循环的前面加个声明:Class x,就好了

 

异常

abort()函数遇到错误情况会直接退出,而不返回main函数

 

try catch

简单来说就是在一个被调用的函数里面写一个条件加throw,然后在try模块中调用,如果出错了就会直接进入相匹配的catch模块。注意这里引用一个栈解退的概念,它和return不一样,它会直接回退到最近的try模块,即中间函数生成的对象会被自动调用析构函数(自动变量元素则被自动释放)。可以利用这个特性,把控制权交给不同的函数。

 

其他异常特性

可以把error变成一个对象,然后直接throw 一个problem对象,注意这里throw的对象是对象的副本。然后catch是这个problem的引用。

#include <iostream>
using namespace std;
class bad_1{
    public:
    virtual ~bad_1(){
        cout << "bad_1 has been destoried" << endl;
    }
    void show_1(){
        cout << "this message comes from bad_1" << endl;
        return;
    }
};
class bad_2:public bad_1{
    public:
    virtual ~bad_2(){
        cout << "bad_2 has been destoried" << endl;
    }
    void show_2(){
        cout << "this message comes from bad_2" << endl;
        return;
    }
};
class bad_3:public bad_2{
    public:
    virtual ~bad_3(){
        cout << "bad_3 has been destoried" << endl;
    }
    void show_3(){
        cout << "this message comes from bad_3" << endl;
        return;
    }
};
void dumpD(int i){
    if(i == 0){
        throw bad_1();
    }else if(i == 1){
        throw bad_2();
    }else{
        throw bad_3();
    }
}
int main(){
    int j;
    cin >> j;
    try{
        dumpD(j);
    }catch(bad_1 &b1){   //注意这里的顺序是错误的!
        b1.show_1();
    }catch(bad_2 &b2){
        b2.show_2();
    }catch(bad_3 &b3){
        b3.show_3();
    }
}

为什么说是错的?当我输入1或者2时,系统抛出的错误来源都是来自bad_1而不是bad_2或者bad_3,原因是因为bad_2/3都是bad_1的派生类,因此catch的第一个是适用于他们的,所以才进入了块一。

正确的是:

int main(){
    int j;
    cin >> j;
    try{
        dumpD(j);
    }catch(bad_3 &b3){
        b3.show_3();
    }catch(bad_2 &b2){
        b2.show_2();
    }catch(bad_1 &b1){   
        b1.show_1();
    }
}

除此之外,还可以像switch一样设置一个默认值:

catch(...){
        cout << "default" << endl;
    }

 

exception 类

1.stdexcept 类:你可以自己写一个异常类,继承std中的exception类,然后重定义what()方法

class bad_my :public std::exception{
        public:
            const char * what(){return "bad news created by myself"}
    }
try{
   /*code*/
}catch(exception &e){
   cout << e.what() << endl;
}

通过继承exception类又诞生出几种错误类型:

logic_error 类

  • domain_error 定义域/值域错误
  • length_error 没用足够空间来执行操作
  • out_of_bounds 通常用于指示索引错误
  • invalid_argument  传递了一个以外的值

runtime_error 类

  • range _error
  • overflow_error
  • underflow_error

通常runtime_error表明存在无法避免的问题

 

2.bad_alloc 和 new

当new申请的空间过大时会抛出这个bad_alloc类,这个类里面的what()方法返回“std::bad_alloc”字符串

重定义退出的方向

未捕获异常:简单来说就是出错后,可以自行定义怎么退出,可以自己定义一个没用参数,没用返回值的函数,然后把函数名传给set_terminate( myQuit )

意外异常:这里说一下怎么捕获所有的异常

#include <exception>
    using namespace std;
    void myUnexpected(){
        throw std::bad_exception(); //或者只写throw
    }

 set_unexpected(myUnexpected); //在开头写

 

内存泄漏

就是在throw exception 后控制权会返回到try后面的catch模块那里,在函数进行过程中创造的类会自动调用析构函数,但是假如在前面使用了new去申请内存,那么这时就会把指针给删掉,指针指向的内存就会没用办法再去用,这就造成了内存泄漏。

 

来自新世界:类模板

先回顾一下鸭!

template <class T> //普通模板
void swap(T &a,T &b)
{
    /*code*/
}
template <class T> //模板重载
void swap(T &a,T &b,int n)
{
    /*code*/
}
                                             //设job是一个结构
template <> void swap <job> (job &a,job &b)  //显示具体化,不加 <job> 也行
{                                            //意思是不要用swap模板生成函数定义
    /*MORE code*/                            //而应该使用专门为job类型显示地定义地函数声明
}

template void swap <double>(double &a,double &b) //显示实例化,实例化模板,当遇到参数是double时用
{                                                  //可以用在参数的强制转换上
    /*code*/
}

template <class T1,class T2> //普通模板
void add(T1 x,T2 y)
{
    decltype(x + y) result = x + y;  //确定x+y的结果的类型
    decltype(function(3)) m;   //确定函数返回值的类型(假如函数是个模板)
}

template <class T1,class T2> //普通模板
auto add(T1 x,T2 y)->decltype(x+y) //确保返回值是和结果一样的类型
{
    return x + y;
}

很好,接下来就是类模板了:

template <class Type>
class Stack
{
private:
    enum {MAX = 10};    // constant specific to class
    Type items[MAX];    // holds stack items
    int top;            // index for top stack item
public:
    Stack();
    bool isempty();
    bool isfull();
    bool push(const Type & item); // add item to stack
    bool pop(Type & item);        // pop top into item
};

template <class Type>
Stack<Type>::Stack()
{
    top = 0;
}

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;
        return true;
    }
    else
        return false;
}

template <class Type>
bool Stack<Type>::pop(Type & item)
{
    if (top > 0)
    {
        item = items[--top];
        return true;
    }
    else
        return false; 
}

可以看出类模板的每个方法都是模板函数,而且方法前变成了 Stack<Type>::

而且注意的是必须显示地提供所需的类型:

Stack<int> A;
Stack<double> B;

那么这个类模板有什么卵用呢?

书上提到了一个远古的数据结构,就是栈(不是

想一想栈这个东西确实用的多,把它弄成什么东西都能装的栈就不用去一个一个地去写了,这里给出一个比较棘手的关于字符串的栈:

template <class Type>
class Stack
{
private:
    enum {SIZE = 10};    // default size
    int stacksize;
    Type * items;       // 其实是任一类型的指针
    int top;            // index for top stack item
public:
    explicit Stack(int ss = SIZE);
    Stack(const Stack & st);
    ~Stack() { delete [] items; }
    bool isempty() { return top == 0; }
    bool isfull() { return top == stacksize; }
    bool push(const Type & item);   // add item to stack
    bool pop(Type & item);          // pop top into item
    Stack & operator=(const Stack & st);
};

template <class Type>
Stack<Type>::Stack(int ss) : stacksize(ss), top(0)
{
    items = new Type [stacksize];    //创建了任意类型的数组,同时用成员items去跟踪
}

template <class Type>
Stack<Type>::Stack(const Stack & st)
{
    stacksize = st.stacksize;
    top = st.top;
    items = new Type [stacksize];
    for (int i = 0; i < top; i++)
        items[i] = st.items[i];
}

template <class Type>
bool Stack<Type>::push(const Type & item)
{
    if (top < stacksize)
    {
        items[top++] = item;    //入栈操作:其实就是把items(此时items是一个指向字符串的指针数组)中的格子设成指针
        return true;            //指向被“压进”栈的字符串
    }
    else
        return false;
}

template <class Type>
bool Stack<Type>::pop(Type & item)
{
    if (top > 0)
    {
        item = items[--top];
        return true;
    }
    else
        return false;
}

template <class Type>
Stack<Type> & Stack<Type>::operator=(const Stack<Type> & st)
{
    if (this == &st)
        return *this;
    delete [] items;
    stacksize = st.stacksize;
    top = st.top;
    items = new Type [stacksize];
    for (int i = 0; i < top; i++)
        items[i] = st.items[i];
    return *this; 
}
// stkoptr1.cpp -- testing stack of pointers
#include <iostream>
#include <cstdlib>     // for rand(), srand()
#include <ctime>       // for time()
#include "stcktp1.h"
const int Num = 10;
int main()
{
    std::srand(std::time(0)); // randomize rand()
    std::cout << "Please enter stack size: ";
    int stacksize;
    std::cin >> stacksize;
// create an empty stack with stacksize slots
    Stack<const char *> st(stacksize); 

// in basket
    const char * in[Num] = {
            " 1: Hank Gilgamesh", " 2: Kiki Ishtar",
            " 3: Betty Rocker", " 4: Ian Flagranti",
            " 5: Wolfgang Kibble", " 6: Portia Koop",
            " 7: Joy Almondo", " 8: Xaverie Paprika",
            " 9: Juan Moore", "10: Misha Mache"
            };
 // out basket
    const char * out[Num];

    int processed = 0;
    int nextin = 0;
    while (processed < Num)
    {
        if (st.isempty())
            st.push(in[nextin++]);
        else if (st.isfull())
            st.pop(out[processed++]);
        else if (std::rand() % 2  && nextin < Num)   // 50-50 chance
            st.push(in[nextin++]);
        else
            st.pop(out[processed++]);
    }
    for (int i = 0; i < Num; i++)
        std::cout << out[i] << std::endl;

    std::cout << "Bye\n";
    // std::cin.get();
    // std::cin.get();
	return 0; 
}

而且转念一想,要有“适应多种数据类型”的特性的模块,不就是基类吗?因此基类通常可以设置为类模板

你比如,我想存两个类型未定的值,这时就可以用模板了:

#include <iostream>
#include <string>
template <class T1, class T2>
class Pair
{
private:
    T1 a;   
    T2 b;
public:
    T1 & first();
    T2 & second();
    T1 first() const { return a; }
    T2 second() const { return b; }
    Pair(const T1 & aval, const T2 & bval) : a(aval), b(bval) { }
    Pair() {}
};

template<class T1, class T2>
T1 & Pair<T1,T2>::first()
{
    return a;
}
template<class T1, class T2>
T2 & Pair<T1,T2>::second()
{
    return b;
}

int main()
{
    using std::cout;
    using std::endl;
    using std::string;
    Pair<string, int> ratings[4] =         //类比int x[4]
    {
        Pair<string, int>("The Purpled Duck", 5),           //注意这里要用Pair<string,int>来调用构造函数
        Pair<string, int>("Jaquie's Frisco Al Fresco", 4),
        Pair<string, int>("Cafe Souffle", 5),
        Pair<string, int>("Bertie's Eats", 3)
    };

    int joints = sizeof(ratings) / sizeof (Pair<string, int>);
    cout << "Rating:\t Eatery\n";
    for (int i = 0; i < joints; i++)
        cout << ratings[i].second() << ":\t "
             << ratings[i].first() << endl;
    cout << "Oops! Revised rating:\n";
    ratings[3].first() = "Bertie's Fab Eats";
    ratings[3].second() = 6;
    cout << ratings[3].second() << ":\t "
         << ratings[3].first() << endl;
   // std::cin.get();
   return 0; 
}

类模板除了和模板函数有什么隐式具体化,显示/隐式实例化之外,还有部分具体化,说白了就是在限制了模板的通用性,详情请看 C++ Primer Plus 583页。现在感觉暂时用不到….

 

成员模板

然鹅更牛皮的是,还有个东西叫成员模板,就是在模板类里面加载个模板类

// tempmemb.cpp -- template members
#include <iostream>
using std::cout;
using std::endl;

template <typename T>
class beta
{
private:
    template <typename V>  // nested template class member
    class hold
    {
    private:
        V val;
    public:
        hold(V v  = 0) : val(v) {}
        void show() const { cout << val << endl; }
        V Value() const { return val; }
    };
    hold<T> q;             // 未确定类型的模板对象
    hold<int> n;           // 确定了类型的模板对象
public:
    beta( T t, int i) : q(t), n(i) {}
    template<typename U>   // template method
    U blab(U u, T t) { return (n.Value() + q.Value()) * u / t; }  //返回值的类型由第一个参数决定
    void Show() const { q.show(); n.show();}
};

int main()
{
    beta<double> guy(3.5, 3);
    cout << "T was set to double\n";
    guy.Show();
    cout << "V was set to T, which is double, then V was set to int\n";
    cout << guy.blab(10, 2.3) << endl;
    cout << "U was set to int\n";
    cout << guy.blab(10.0, 2.3) << endl;
    cout << "U was set to double\n";
    cout << "Done\n";
    // std::cin.get();
    return 0; 
}

 其中不同颜色代表不同的数据类型

 

 

还没完,这个模板还可以当成参数传进去:

// tempparm.cpp � templates as parameters
#include <iostream>
#include "stacktp.h"

template <template <typename T> class Thing>    //在此例中是把stack模板传进去 template <class type>class stack
class Crab
{
private:
    Thing<int> s1;   //实例化为 stack<int> s1
    Thing<double> s2;   //实例化为 stack<double> s2
public:
    Crab() {};
    // assumes the thing class has push() and pop() members
    bool push(int a, double x) { return s1.push(a) && s2.push(x); }
    bool pop(int & a, double & x){ return s1.pop(a) && s2.pop(x); }
};
    
int main()
{
    using std::cout;
    using std::cin;
    using std::endl;
    Crab<Stack> nebula;
// Stack must match template <typename T> class thing   
    int ni;
    double nb;
    cout << "Enter int double pairs, such as 4 3.5 (0 0 to end):\n";
    while (cin>> ni >> nb && ni > 0 && nb > 0)
    {
        if (!nebula.push(ni, nb))
            break;
    }
   
    while (nebula.pop(ni, nb))
           cout << ni << ", " << nb << endl;
    cout << "Done.\n";
    // cin.get();
    // cin.get();
    return 0; 
}

很明显的看到了,这种模板之间的组合极大的增加了灵活性与代码的重用

 

 

模板类的友元

 

  • 非模板的友元
  • 约束友元模板,即友元的类型取决于类被实例化时的类型
  • 非约束的模板友元,即友元的所有具体化都是类的每一个具体化的友元

 

1.非模板的友元(非模板函数):

template <class T>
class Test
{
    friend void reprot(Test<T> &);
    //friend void reprot(Test &);   错误!Test对象只有特定的具体化
};

并且在友元函数的实现中也要给定参数:

void reprot(Test<int> &){
   /*code*/
}
void reprot(Test<double> &){
   /*code*/
}

看上去就是友元函数的重载不是吗?

 

2.约束模板友元函数(模板函数):

先声明模板函数,再在模板类里面声明为友元:

friend void counts<T>();
friend void report<>(Father<T> &);  //创建一个模板为Father<int>/<double>/<char>....的友元模板函数

完整:

// tmp2tmp.cpp -- template friends to a template class
#include <iostream>
using std::cout;
using std::endl;

// template prototypes
template <typename T> void counts();
template <typename T> void report(T &);

// template class
template <typename TT>
class HasFriendT
{
private:
    TT item;
    static int ct;
public:
    HasFriendT(const TT & i) : item(i) {ct++;}
    ~HasFriendT() { ct--; }
    friend void counts<TT>();
    friend void report<>(HasFriendT<TT> &);
};

template <typename T>
int HasFriendT<T>::ct = 0;

// template friend functions definitions
template <typename T>
void counts()
{
    cout << "template size: " << sizeof(HasFriendT<T>) << "; ";
    cout << "template counts(): " << HasFriendT<T>::ct << endl;
}

template <typename T>
void report(T & hf)
{
    cout << hf.item << endl;
}

int main()
{
    counts<int>();
    HasFriendT<int> hfi1(10);
    HasFriendT<int> hfi2(20);
    HasFriendT<double> hfdb(10.5);
    report(hfi1);  // generate report(HasFriendT<int> &)
    report(hfi2);  // generate report(HasFriendT<int> &)
    report(hfdb);  // generate report(HasFriendT<double> &)
    cout << "counts<int>() output:\n";
    counts<int>();
    cout << "counts<double>() output:\n";
    counts<double>();
    // std::cin.get();
    return 0; 
}

 

3.非约束模板的友元函数

// manyfrnd.cpp -- unbound template friend to a template class
#include <iostream>
using std::cout;
using std::endl;

template <typename T>
class ManyFriend
{
private:
    T item;
public:
    ManyFriend(const T & i) : item(i) {}
    template <typename C, typename D> friend void show2(C &, D &);   
};

template <typename C, typename D> void show2(C & c, D & d)
{
    cout << c.item << ", " << d.item << endl;
}

int main()
{
    ManyFriend<int> hfi1(10);
    ManyFriend<int> hfi2(20);
    ManyFriend<double> hfdb(10.5);
    cout << "hfi1, hfi2: ";
    show2(hfi1, hfi2);
    cout << "hfdb, hfi2: ";
    show2(hfdb, hfi2);
    // std::cin.get();
    return 0;
}

我们可以看出,非约束的模板友元函数可以随着传进函数的参数的类型而生成相应的模板,“模板函数”这个特点更加明显,不再依赖于模板类的具体类型。这让这个函数可以处理各种各样的数据。

反观上面的约束模板友元函数,它生成的具体模板仅于模板类有关,因此说是 “被约束的”,作用我想是增加类模板的灵活性。

 

构造一个类和声明函数的混淆

哈哈哈,高浩裕看出来的

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

class A{
    public:
        A(int x):a(x=0){}
        void printA(){
            cout<<"a="<<a<<endl;
        }
    private:
        int a;
};

int main(){
    A objA;
    objA.printA();
    return 0;
}

当代码是这样子时,过不了

但是当代码改一下变成:

A objA();

当只有这一句时过了,然鹅并不能调用成员函数,这就很蛋疼

但是为什么可以过呢?

这一句其实不是声明一个A类,而是声明一个函数,它没用参数,返回一个A类的对象!

正确代码:

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

class A{
    public:
        A(int x=0):a(x){}
        void printA(){
            cout<<"a="<<a<<endl;
        }
    private:
        int a;
};

int main(){
    A objA;
    objA.printA();
    return 0;
}

 

 

来自新世界:多重继承

先引入一个问题..

这时我们可以发现,创建一个“劳动者”A,再用“人”的指针去调用这个“劳动者”A 时会发现有两个“人”对象—-“主播”的“人” 和 “老师” 的 “人”

这时我们就要用强制转换来指明这个指针到底要指向谁:

Worker ed;
Human *pw1 = (Teacher *)&ed;
Human *pw2 = (Singer *)&ed;

这样就很蛋疼,因此我们有了虚基类:

在singer 和 teacher 类的继承部分加个virtual ,就行了,这样做会使 worker 类只继承一次 human 类。

当然这里相对的就要引进新的构造函数:

worker(const human &hu, string &na, string &num)
    : human(hu), teacher(hu,na), singer(hu,num){}

然鹅,当我们想要打印出某位劳动者的信息时,我们要怎么定义show()函数呢?

void worker::show()
{
    singer::show();
    teacher::show();
}

这样做会使human基类里面的show() 函数被调用了两次,因此应该进行模块化设计:

void human::data()const{
    cout << "name:" << name << endl;
}
void teacher::data()const{
    cout << "school:" << school << endl;
}
void singer::data()const{
    cout << "company" << company << endl;
}
void worker::data()const{
    singer::data();
    teacher::data();
}
void worker::show(){
    cout << "Category:" << endl;
    human::data();
    data();
}

这种模块化设计可以把上述的data设为私有函数,协助公有接口。