程序设计与算法郭炜老师的课堂笔记3

  • 从C到C++
    • 引用
      • 引用作为函数参数
      • 引用作为函数返回值
      • 常引用
    • const关键字
      • 定义常量
      • 定义常量指针
      • 定义常引用
    • 动态内存分配
      • 用new开内存
      • 用delete释放内存
    • 内联函数
    • 函数重载
    • 函数缺省参数
  • 类和对象
    • 使用类的成员变量和成员函数
    • 类的成员函数和类的定义分开写
    • 类成员可访问范围
      • 访问范围关键字
      • 类成员的可访问范围
    • 成员函数的重载与参数缺省
    • 构造函数
      • 概念
      • 一个类可以用多个构造函数
      • 为什么需要构造函数
      • 构造函数在数组中使用
      • 复制构造函数
        • 概念
        • 作用
        • 常量引用参数的使用
      • 类型转换构造函数
    • 析构函数
      • 析构函数和构造函数调用时机
    • this指针
    • 静态成员变量和静态成员函数
      • 静态成员变量
      • 静态成员函数
      • 访问静态成员
    • 成员对象和封闭类
      • 封闭类构造函数和析构函数的执行顺序
      • 封闭类的复制构造函数
    • 常量对象
    • 常量成员函数
      • 常量成员函数的重载
      • 常引用
    • 友元
      • 友元函数
      • 友元类
  • 类型自动转换
  • 运算符重载
    • 基本概念
    • 赋值运算符重载
    • 运算符重载为友元函数
    • 可变数组类的实现
    • 流插入运算符的重载
      • 更改输出格式
    • 重载类型转换运算符
    • 自增自减运算符重载
  • 继承
    • 继承关系
    • 复合关系
    • 保护成员protected
    • 派生类的构造函数
    • public的赋值兼容规则
  • 多态
    • 虚函数
    • 虚函数表
    • 虚析构函数
    • 纯虚函数
    • 抽象类
  • 输入输出
    • 输出重定向
    • istream类
    • 流操作算子
      • 整数流的基数
      • 控制浮点数精度的流操作算子
      • 设置域宽的流操作算子
      • 综合
      • 用户自定义流操作算子
  • 文件读写
    • 创建文件
    • 文件读写指针
    • 字符文件读写
    • 二进制文件读写
      • 二进制读文件
      • 二进制写文件
      • 在文件中写入和读取一个整数
      • 从键盘输入几个学生的性名和成绩,并以二进制文件形式保存
      • 读取此文件
      • 将Jane改为Mike
      • 文件拷贝
  • 模板
    • 模板函数
      • 多个类型参数
      • 不通过参数实例化函数模版
      • 函数模版重载
      • 函数模版和函数次序
    • 类模版
      • 函数模板作为类模板成员
      • 类模板与非类型参数
      • 类模板与继承
      • 类模板与友元
      • 类模板与静态成员变量
  • String类
  • 函数对象

从C到C++

引用

类型名 & 引用名 = 某变量名

int n = 4;
int & r = n;//r引用了n,r的类型是int&r = 4;
cout<<r;//输出4
cout<<n;//输出4
n = 5;
cout<<r;//输出5

引用是从一而终

引用作为函数参数

//在C中交换函数
void swap(int*a,int*b)
{int tmp;tmp=*a;*a=*b;*b=tmp;
}
int n1,n2;
swap(&n1,&n2);
//在C++中交换
void swap(int &a,int &b)
{//构建两个引用参数int tmp;tmp=a;a=b;b=tmp;
}
int n1,n2;
swap(n1,n2);

引用作为函数返回值

int n = 4;
int & SetValue()
{return n;
}
int main()
{SetValue()=40;cout<<n;return 0;
}//输出:40

常引用

int n;
const int &r = n;//r的类型是const int &

不能通过常引用去修改其引用的内容

int n = 100;
const int &r = n;
r = 300;//出错

const关键字

定义常量

const int MAX_VAL = 23;
const double Pi = 3.14;
const char * SCHOOL_NAME = "BIT";

定义常量指针

不可通过常量指针修改其指向的内容

int n,m;
const int *p = &n;
*p = 5;//非法
n = 4;
p = &m;//常量指针的指向可以变化

不能把常量指针赋值给非常量指针,反过来可以

const int *p1;
int *p2;
p1 = p2;//ok
p2 = p1;//非法,因为常量内容不可修改,所以不能把地址告诉非常量指针从而使得它来修改常量
p2 = (int*)p1;//强制类型转化

函数参数为常量指针时,可避免函数内部不小心改变参数指针所指地方的内容

void MyPrintf(const char*p)
{strcpy(p,"this");//报错printf("%s",p);
}

定义常引用

动态内存分配

用new开内存

  • 分配一个变量:
    P = new T;
    T是任意类型名,P是类型名为T*的指针
    动态分配出一片大小为sizeof(T)字节的内存空间,并且将该内存空间的起始地址赋值给P

    int *pn;
    pn = new int;
    *pn = 5;
    
  • 分配一个数组:
    P = new T[N];
    T:任意类型名
    P:类型为T*的指针
    N:要分配的数组元素的个数,可以是类型表达式
    动态分配出一片大小为N*sizeof(T)字节的内存空间,并将该内存空间的起始地址赋值给P

    int *pn;
    int i = 5;
    pn = new int[i * 20];
    pn[0] = 20;
    

用delete释放内存

delete 指针;//该指针必须指向new出来的空间

int *p = new int;
*p = 5;
delete p;
delete p;//只能释放一次

delete [ ]指针;

int *p = new int[20];
p[0] = 1;
delete [] p;

内联函数

函数调用是有时间开销的,如果函数本身只有几条语句,执行非常快,而且函数被反复执行很多次,相比之下调用函数所产生的这个开销就会显得比较大,不用参数入栈
为了减少函数调用的开销,引入了内敛函数机制
编译器处理对内联函数的调用语句时,是将整个函数的代码插入到调用语句处,而不会产生调用函数的语句

inline int Max(int a,int b)
{if(a > b){return a;}return b;
}

函数重载

一个或者多个函数,名字相同,然而参数个数或参数类型不相同
编译器根据调用语句的中的实参的个数和类型判断应该调用哪个函数

int Max(double f1,double f2){}
int Max(int n1,int n2){}
int Max(int n1,int n2,int n3){}Max(3.4,2.5;//1
Max(2,4);//2
Max(1,2,3);//3
Max(3,2.4);//报错

函数缺省参数

定义函数的时候可以让最右边的连续若干个参数有缺省值

void func(int x1,int x2 = 2,int x3 = 3){}func(10);//func(10,2,3)
func(10,8);//func(10,8,3)
func(10,,8);//非法,只能最右边的连续若干个参数缺省

类和对象

输入矩形的长和宽,输出面积和周长
长宽变量和得到长宽数据,求面积,求体积三个函数封装到矩形类
长宽变量称为该矩形类的成员变量,三个函数称为该类的成员函数
成员变量和成员函数统称为类的成员
实际上,“类"看上去就像"带函数的结构”

class CRectangle
{//进行一波封装public:int w,h;int Area(){return w*h;}int Perimeter(){return 2*(w+h);}void Init(int w_,int h_){w = w_;h = h_;}
};int main()
{int w,h;CRectangle r;//r是一个对象cin>>w>>h;r.Init(w,h);cout<<r.Area()<<endl<<r.Perimeter()<<endl;return 0;
}

使用类的成员变量和成员函数

  • 对象名.成员名

    CRectangle r1,r2;
    r1.w = 5;
    r2.Init(5,4);
    
  • 指针->成员名

    CRectangle r1,r2;
    CRectangle *p1 = & r1;
    CRectangle *p2 = & r2;
    p1->w =5;
    p2->Init(5,4);
    
  • 引用名.成员名

    CRectangle r2;
    CRectangle &rr = r2;
    rr.w = 5;
    rr.Init(5,4);//rr值改变,则r2值也改变
    

类的成员函数和类的定义分开写

class CRectangle
{//进行一波封装public:int w,h;int Area();int Perimeter();void Init(int w_,int h_);
};
int CRectangle::Area()
{return w*h;
}
int CRectangle::erimeter()
{return 2*(w+h);
}
void CRectangle::Init(int w_,int h_)
{w = w_;h = h_;
}

类成员可访问范围

访问范围关键字

  • private:私有成员,只能在成员函数内访问,缺省值
  • public:公有成员,可以在任何地方访问
  • protected:保护成员

类成员的可访问范围

  • 在类的成员函数内部,能够访问

    • 当前对象的全部属性,函数
    • 同类其它对象的全部属性,函数
  • 在类的成员函数以外的地方,只能够访问该类对象的公有成员
class CEmployee
{
private:char szName[30];
public:int salary;void setName(char* name);void getName(char* name);void averageSalary(CEmployee e1, CEmployee e2);
};void CEmployee::setName(char* name)
{strcpy(szName, name);//ok
}void CEmployee::getName(char* name)
{strcpy(name, szName);//ok
}void CEmployee::averageSalary(CEmployee e1, CEmployee e2)
{cout << e1.szName;//ok,访问同类其它对象私有成员salary = (e1.salary + e2.salary) / 2;
}int main()
{CEmployee e;strcmp(e.szName, "Tom");//no,不能访问私有成员e.setName("Tom");e.salary = 5000;return 0;
}

设置私有成员的机制,叫"隐藏"
隐藏的目的是强制对成员变量的访问一定要通过成员函数进行,那么以后成员变量的类型等属性修改后,只需要更改成员函数即可.否则所有直接访问成员变量的语句都需要修改

成员函数的重载与参数缺省

构造函数

概念

  • 成员函数的一种

    • 名字与类名相同,可以有参数,不能有返回值(void也不行)
    • 作用是对对象进行初始化,如给成员变量赋初值
    • 如果定义类时没写构造函数,则编译器生成一个默认的无参数的构造函数
      • 默认构造函数无参数,不做任何操作
  • 如果定义了构造函数,则编译器不生成默认的无参数的构造函数
  • 对象生成时构造函数自动被调用.对象一旦生成,就再也不能在其上执行构造函数
class Complex
{
private:double real, imag;
public:void Set(double r, double i);
};//编译器会自动生成默认构造函数Complex c1;//默认构造函数被调用,无参数
Complex* pc = new Complex;//默认构造函数被调用,无参数
class Complex
{
private:double real, imag;
public:Complex(double r, double i=0);
};Complex::Complex(double r, double i)
{real = r;imag = i;
}Complex c1;//报错,缺少构造函数的参数
Complex* pc = new Complex;//报错,没有参数
Complex c1(2);//ok
Complex c1(2,4);//ok
Complex* pc = new Complex(3,4);//ok

一个类可以用多个构造函数

class Complex
{
private:double real, imag;
public:void Set(double r, double i);Complex(double r);Complex(double r, double i=0);Complex(Complex c1, Complex c2);
};Complex::Complex(double r, double i)
{real = r;imag = i;
}Complex::Complex(double r)
{real = r;imag = 0;
}Complex::Complex(Complex c1, Complex c2)
{real = c1.real + c2.real;imag = c1.imag + c2.imag;
}Complex c1(3);//{3,0}
Complex c2(1, 2);//{1,2}
Complex c3(c1, c2);//{4,2}

为什么需要构造函数

  • 构造函数执行必要的初始化工作,有了构造函数,就不必专门再写初始化函数,也不用担心忘记调用初始化函数
  • 有时对象没被初始化就使用,会导致程序出错

构造函数在数组中使用

class CSample
{int x;
public:CSample(){cout << "Constructor 1 Called" << endl;}CSample(int n){x = n;cout << "Constructor 2 Called" << endl;}
};int main()
{CSample array1[2];//1 1CSample array2[2] = { 4,5 };//2 2CSample array3[2] = { 3 };//2 1CSample* array4 = new CSample[2];//1 1delete[]array4;return 0;
}
class Test
{
public:Test(int n) {}       //(1)Test(int n, int m) {}//(2)Test() {}             //(3)
};Test array1[3] = { 1,Test(1,2) };//1 2 3
Test array2[3] = { Test(3,2),Test(1,2),1 };//2 2 1
Test* pArray[3] = { new Test(4),new Test(1,2) };//1 2
//上面这个语句只会生成两个对象
//因为这是指针创建的,如果为指定含义则指针为空而不创建

复制构造函数

概念

  • 只有一个参数,即对同类对象的引用

  • 形如X::X(X&)X::X(const X&),二者选一
    后者能以常量对象作为参数,使用更多

    class Complex
    {
    private:double real, imag;
    };Complex c1;
    //调用缺省无参构造函数
    Complex c2(c1);
    //调用缺省的无复制构造函数,将c2初始化成和c1一样
    
  • 如果没有定义复制构造函数,那么编译器生成默认复制构造函数.默认的复制构造函数完成复制功能

    class Complex
    {
    public:double real, imag;Complex() {}Complex(const Complex& c){real = c.real;imag = c.imag;cout << "Copy来的";}
    };
    Complex c1;
    Complex c2(c1);
    

作用

  • 用一个对象去初始化同类的另一个对象时

  • 某函数有一个参数是类A的对象,那么该函数被调用时,类A的复制构造函数将被调用

  • 如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数将被调用

    class A
    {
    public:int v;A(int n){v = n;};A(const A &a){v = a.v;cout << "OK";}
    };A Func()
    {A b(4);return b;
    }int main()
    {cout << Func().v << endl;//ok4return 0;
    }
    

注意:对象间赋值并不导致复制构造函数被调用

class CMyclass
{
public:int n;CMyclass(){};CMyclass(CMyclass &c){n = 2 * c.n;}
};int main()
{CMyclass c1, c2;c1.n = 5;c2 = c1;CMyclass c3(c1);cout << c2.n;//5cout << c3.n;//10
}

常量引用参数的使用

void fun(CMyclass obj_)
{cout << "ok" << endl;
}void fun(const CMyclass &obj)

这样的函数在调用的时候生成形参会引发复制构造函数调用,开销比较大
可以考虑使用CMyclass&引用类型作为参数
希望确保实参的值在函数中不应被改变,那么可以加上const

类型转换构造函数

  • 目的是实现类型的自动转换
  • 只有一个参数,而且不是复制构造函数的构造函数,就是转换构造函数
  • 当需要的时候编译器会自动调用转换构造函数,建立一个无名的临时对象
class Complex
{
public:double real, imag;Complex(int i){//类型转换构造函数cout << "转换" << endl;real = i;imag = 0;}Complex(double r, double i){real = r;imag = i;}
};int main()
{Complex c1(7, 8);Complex c2 = 12;c1 = 9;//本身是类型不匹配,但是因为有了类型转化函数后就被转换成一个临时的Complex对象cout << c1.real << c1.imag << endl;
}//转换9 0

析构函数

  • 名字与类名相同,在前面加 ~ ,没有参数和返回值,一个类只能有一个析构函数
  • 析构函数对象消亡时即自动被调用.可以定义析构函数来在对象消亡前做善后工作,比如释放内存空间等
  • 如果定义类时候没写析构函数,则编译器生成缺省析构函数.缺省析构函数什么也不做
class String
{
private:char *p;
public:String(){p = new char[10];}~String();
};String::~String()
{delete[]p;
}
  • 对象数组生命周期结束时,对象数组的每个元素的析构函数都会被调用

    class Ctest
    {
    public:~Ctest(){cout << "释放" << endl;}
    };int main()
    {Ctest array[2];cout << "End" << endl;return 0;
    }//释放释放
    
  • delete 运算导致析构函数调用
    若new一个对象数组,那么用delete释放时应该写[ ].否则只delete一个对象

    Ctest * pTest;
    pTest = new Ctest;//构造
    delete pTest;//析构pTest = new Ctest[3];//构造三次
    delete [] pTest;//析构三次
    
  • 析构函数在对象作为函数返回值返回后被调用

    class CMyclass
    {
    public:~CMyclass(){cout << "哦" << endl;}
    };CMyclass obj;CMyclass fun(CMyclass sobj)
    {//sobj这个形参在使用完后会被释放产生一个哦return sobj;
    }int main()
    {obj = fun(obj);//fun(obj)是一个临时对象,在使用完后也会消亡产生哦return 0;//最终程序结束,obj释放产生一个哦
    }//哦哦哦
    

析构函数和构造函数调用时机

class Demo
{int id;
public:Demo(int i){id = i;cout << "生" << id << endl;}~Demo(){cout << "亡" << id << endl;}
};Demo d1(1);//此句最先执行,于是引发构造函数void Func()
{static Demo d2(2);//静态局部变量不会消亡,仍然会保存其值Demo d3(3);cout << "func" << endl;
}int main()
{Demo d4(4);d4=6;cout<<"main"<<endl;{//变量的生存期只在一对大括号内Demo d5(5);//出了这个大括号他就被释放了}cout<<"main end"<<endl;return 0;
}
//生1
//生4
//生6
//亡6
//::因为6这个数字被转换函数转换为Demo类后相当于完成了生成和凋亡的过程
//main
//生5
//亡5
//生2
//生3
//func
//亡3
//main ends
//亡6
//亡2
//亡1
//::先构造的后析构

this指针

  • c++到c的翻译

    //C++
    class CCar
    {
    public:int price;void SetPrice(int p);
    };void CCar::SetPrice(int p)
    {price = p;
    }
    int main()
    {CCar car;car.SetPrice(2000);return 0;
    }
    //翻译成C
    struct CCar
    {int price;
    };void SetPrice(struct CCar * this, int p)
    {this->price = p;
    }int main()
    {struct CCar car;SetPrice( &car, 2000);return 0;
    }
    
  • 非静态成员函数中可以直接使用this来代表指向该函数作用的对象的指针

  • 静态成员函数不能使用
    因为静态成员函数并不具体作用于某个对象
    因此静态成员函数的真是参数的个数,就是程序中写出的参数个数

    class Complex
    {
    public:double real, imag;void Print(){cout << real << "," << imag;}Complex(double r, double i) : real(r), imag(i){}Complex AddOne(){this->real++;//等价于real++this->Print();//等价于Pringtreturn *this;}
    };int main()
    {Complex c1(1, 1), c2(0, 0);c2 = c1.AddOne();return 0;
    }//2,1
    
  • this的意义

    class A
    {int i;
    public:void Hello(){cout << "hello" << endl;}
    };//void Hello(A*this){cout << "hello" << endl;}int main()
    {A *p=NULL;p->Hello();//Hello(p);
    }//输出:hello,不报错
    
    class A
    {int i;
    public:void Hello(){cout << i << "hello" << endl;}
    };//void Hello(A*this){cout << this->i << "hello" << endl;} 此时出现this,而原指针NULL则会报错int main()
    {A *p=NULL;p->Hello();//Hello(p);
    }//报错
    

静态成员变量和静态成员函数

加static关键字

静态成员变量

普通成员变量每个对象有各自的一份
静态成员变量一共就一份,为所有对象共享

sizeof运算符不计算静态成员变量

静态成员函数

普通成员函数必须作用于某个对象
静态成员函数并不具体作用于某个对象

  • 因此静态成员不需要通过对象就能访问

访问静态成员

  • 类名::成员名
    CRectangle::PrintTotal();
  • 对象名.成员名
    CRectangle r;
    r.PrintTotal();
  • 指针->成员名
    CRectangle *p = &r;
    p->PrintTotal();
  • 引用.成员名
    CRectangle & ref = f;
    int n = ref.nTotalNumber;

静态成员变量本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在
静态成员函数本质上是全局函数
设置静态成员这种机制的目的是将和某些类紧密相关的全局变量和函数写到类里面,看上去像是一个整体

比如考虑一个随时需要知道矩形面积和综述的图形处理程序

class CRectangle
{
private:int w, h;static int nTotalArea;static int nTotalNumber;
public:CRectangle(int w_, int h_);~CRectangle();static void PrintTotal();
};CRectangle::CRectangle(int w_, int h_)
{w = w_;h = h_;nTotalNumber++;nTotalArea += w * h;
}CRectangle::~CRectangle()
{nTotalNumber--;nTotalArea -= w * h;
}void CRectangle::PrintTotal()
{cout << nTotalNumber << "," << nTotalArea << endl;
}int CRectangle::nTotalArea = 0;
int CRectangle::nTotalNumber = 0;
/*必须在定义类的文件中对静态成员变量进行一次说明
或初始化。否则编译能通过,链接不能通过*/int main()
{CRectangle r1(3,3),r2(2,2);//cout<<CRectangle::nTotalNumber;错误,私有不可访问CRectangle::PrintTotal();r1.PrintTotal();//照常输出,因为其并不属于某个变量return 0;
}//2,13  2,13
  • 静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数

    void CRectangle::PrintTotal()
    {cout<<w<<endl;
    }//错误,w是非静态的
    
  • 在使用CRectangle类时,有时会调用复制构造函数生成临时的隐藏的CRectangle对象

    • 调用一个以CRectangle类对象作为参数对函数时
    • 调用一个以CRectangle类对象作为返回值的函数时

    这些临时对象在消亡时会调用析构函数,减少nTotalNumber和nTotalArea的值,但这些临时对象在生成时却又没有增加这些值

    解决办法:自己写复制构造函数

    CRectangle::CRectangle(CRectangle &r)
    {w = r.w; h = r.h;nTotalNumber++;nTotalArea += w*h;
    }
    

成员对象和封闭类

有成员对象的类叫封闭类

class CTyre
{//轮胎类
private:int radius;int width;
public:CTyre(int r, int w) : radius(r), width(w){}
};class CEngine
{//引擎类
};class CCar
{//汽车类就是一个封闭类
private:int price;CTyre tyre;CEngine engine;
public:CCar(int p,int tr,int w);
};CCar::CCar(int p, int tr, int w) :price(p),tyre(tr,w)
{
};int main()
{CCar car(2000,17,225);return 0;
}

上例中如果CCar类不定义构造函数,则此语句会编译出错:CCar car;
因为编译器不明白car.type该如何初始化,car.type定义了有参构造函数,而没有无参构造函数

所以任何生成封闭类对象的语句,都要让编译器明白,对象中的成员对象,是如何初始化的

具体的做法就是:通过封闭类的构造函数的初始化列表

成员对象初始化列表中的参数可以是任意复杂的表达式,可以包括函数,变量只要表达式中的函数或变量有定义就行

封闭类构造函数和析构函数的执行顺序

  • 封闭类对象生成时,先执行所有对象成员的构造函数,然后才执行封闭类的构造函数

  • 对象成员的构造函数调用次序和对象成员在类中的说明次序一致,与它们在成员初始化列表中出现的次序无关

  • 当封闭类的对象消亡时,先执行封闭类的析构函数,然后再执行成员对象的析构函数.次序和构造函数的调用次序相反

    class CTyre
    {
    public:CTyre(){cout << "CTyre 创建" << endl;}~CTyre(){cout << "Ctyre 消亡" << endl;}
    };class CEngine
    {
    public:CEngine(){cout << "CEngine 创建" << endl;}~CEngine(){cout << "CEngine 消亡" << endl;}
    };class CCar
    {
    private:CEngine engine;CTyre tyre;
    public:CCar(){cout << "CCar 创建" << endl;}~CCar(){cout << "CCar 消亡" << endl;}
    };int main()
    {CCar car;return 0;
    }
    /*CEngine 创建
    CTyre 创建
    CCar 创建
    CCar 消亡
    Ctyre 消亡
    CEngine 消亡*/
    

封闭类的复制构造函数

说明b2.a是用类A的复制构造函数初始化的.而且调用复制构造函数时的实参就是b1.a

class A
{
public:A(){cout << "default" << endl;}A(A &a){cout << "copy" << endl;}
};class B
{A a;
};int main()
{B b1, b2(b1);return 0;
}//default copy

常量对象

如果不希望某个对象的值被改变,则定义该对象的时候可以在前面加const关键字

const Complxe c1;

常量成员函数

void Complex() const;
  • 在类的成员函数说明后可以加const关键字,则该成员函数称为常量成员函数
  • 常量成员函数执行期间不应修改其所作用的对象.因此在常量成员函数中不能修改成员变量的值(静态成员变量除外),也不能调用同类的非常量成员函数(静态成员函数除外)

常量成员函数的重载

两个成员函数,名字和参数表都一样,但是一个是const,一个不是,算重载

常引用

对象作为函数参数时,生成该参数需要调用复制构造函数,效率低.指针作参数,代码不好看 const Complex & C1

友元

友元函数

一个类的友元函数可以访问该类的私有成员

class CCar;//提前声明CCar类,以便后面的CDrive类使用class CDriver
{
public:void ModifyCar(CCar *pCar);//改装汽车
};class CCar
{
private:int price;friend int MostExpensiveCar(CCar cars[], int total);//声明友元friend int CDriver::ModifyCar(CCar *pCar);//声明友元
};void CDriver::ModifyCar(CCar *pCar)
{pCar->price += 1000;
}int MostExpensiveCar(CCar cars[], int total)
{//求最贵汽车的价格int tmpMax = -1;for (int i = 0; i < total; ++i){if (cars[i].price > tmpMax){tmpMax = cars[i].price;}}return tmpMax;
}int main()
{return 0;
}

友元类

如果A是B的友元类,那么A的成员函数可以访问B的私有成员

class CCar
{
private:int price;friend class CDriver;
};class CDriver
{
public:CCar myCar;void ModifyCar(){//因CDriver是CCar的友元类,故此处可以访问其私有成员myCar.price+=1000;}
};int main()
{return 0;
}

友元类之间的关系不能传递,不能继承

类型自动转换

operator double() const
{//自动类型转换,可以将此类转换为double类型return num;
}operator Circle() const
{//自动类型转化,将此类的类型转换为Circle类型return Circle(num);
}

运算符重载

基本概念

例如可以实现对象与对象之间的加减

返回值类型 operator 运算符(形参表)
{...
}
  • 重载为成员函数时,参数个数为运算符目数减一
  • 重载为普通函数时,参数个数为运算符目数
class Complex
{
public:double real, imag;Complex(double r = 0.0, double i = 0.0) : real(r), imag(i){}Complex operator-(const Complex &c);
};Complex operator+(const Complex &a,const Complex &b)
{return Complex(a.real+b.real,a.imag+b.imag);
}//返回一个临时对象Complex Complex::operator-(const Complex &c)
{return Complex(real -c.real,imag-c.imag);
}//返回一个临时对象int main()
{Complex a(4, 4), b(1, 1), c;c = a + b;//等价于c=operator+(a,b)cout << c.real << "," << c.imag << endl;cout << (a - b).real << "," << (a - b).imag << endl;//a - b等价于a.operator-(b)return 0;
}//5,5 3,3

赋值运算符重载

有时希望赋值运算符两边的类型可以不匹配,比如,把int类型赋值给一个Complex对象,或把一个char *类型的字符串赋值给一个字符串对象,此时需要重载赋值运算符 =

赋值运算符 = 只能重载为成员函数

class String
{
private:char *str;
public:String() : str(new char[1]){str[0] = 0;}const char *c_str(){return str;}String &operator=(const char *s);String::~String(){delete[] str;}
};String &String::operator=(const char *s)
{//重载 = 以使得 obj = Hello 能够成立delete[] str;str = new char[strlen(s) + 1];strcpy(str, s);return *this;
}int main()
{String s;s = "GoodLuck";//等价于s.operator=("GoodLuck");cout << s.c_str() << endl;String s2 = "hello";//这条语句是初始化语句不是赋值语句,会出错return 0;
}
  • 如不定义自己的赋值运算符,那么s1=s2实际上导致s1.str和s2.str指向同一个地方

    String s1,s2;
    s1 = "this";
    s2 = "that";
    s1 = s2;//导致s1的指针指向了s2,s1此时为内存垃圾
    
  • 如果s1对象消亡,析构函数将释放s1.str指向的空间,则s2消亡时还要释放一次,不妥

  • 另外,如果执行s1 = “other”,会导致s2.str指向的地方被delete

  • 因此要在class String中添加成员函数

    String & operator = (const String & s)
    {delete [] str;str = new char[strlen(s.str) + 1];strcpy( str, s.str);return * this;
    }
    
  • 但是如果运行一下代码还是会出错

    MyString s;
    s = "Hello";
    s = s;
    

    应修改为

    String & operator = (const String & s)
    {if( this == & s){//防止左右两边一样return * this;}delete [] str;str = new char[strlen(s.str) + 1];strcpy( str, s.str);return * this;
    }
    

运算符重载为友元函数

class Complex
{double real, imag;
public:Complex(double r, double i) : real(r), imag(i){};Complex operator+(double r);
};
Complex Complex::operator+( double r)
{//可以解释c+5,但是不能解释5+creturn Complex(real + r,imag);
}

一般来说重载为成员函数不能满足使用要求,重载为普通函数又不能访问类的私有成员,所以需要将运算符重载为友元

//想要解释5+c,需将+重载为普通函数
Complex operator + (double r,const Complex & c)
{return Complex( c.real + r, c.imag);
}//在Complex类里需要如下定义,来实现访问
friend Complex operator + (double r,const Complex & c);

可变数组类的实现

编写一个类,能如下使用

int main()
{CArray a;//开始数组是空的for (int i = 0; i < 5; ++i){a.push_back(i);
//要用动态分配的内存来存放数组元素,需要一个指针成员变量}CArray a2, a3;a2 = a;//要重载"="for (int i = 0; i < a.length(); ++i){cout << a2[i] << " ";//要重载"[]"}cout << endl;a2 = a3;//a2是空的for (int i = 0; i < a2.length(); ++i){//a2.length()返回0cout << a2[i] << " ";}cout << endl;a[3] = 100;CArray a4(a);for (int i = 0; i < a4.length(); ++i){cout << a4[i] << " ";}return 0;
}
//0 1 2 3 4
//0 1 2 100 4
class CArray
{int size;//数组个数int *ptr;//指向动态分配的数组
public:CArray(int s = 0);//s代表数组元素个数CArray(CArray &a);~CArray();void push_back(int v);//用于在数组位补添加一个元素vCArray &operator=(const CArray &a);//用于数组对象间的赋值int length(){//返回数组元素个数return size;}int & CArray::operator[](int i)//返回值为int不行,不支持a[i]=4//用以支持根据下标访问数组元素//如n = a[i]和a[i] = 4;这样的语句{return ptr[i];}
};CArray::CArray(int s) : size(s)
{if (s == 0){ptr = NUll;}else{ptr = new int[s];}
}CArray::CArray(CArray &a)
{if (!a.ptr){ptr = NULL;size = 0;return;}ptr = new int[a.size];memcpy(ptr, a.ptr, sizeof(int) * a.size);size = a.size;
}CArray::~CArray()
{if (ptr){delete[] ptr;}
}void CArray::push_back(int v)
{//在数组尾部添加一个元素if(ptr){int *tmpPtr=new int [size+1];//重新分配空间memcpy(tmpPtr,ptr,sizeof(int)*size);//拷贝原数组内容delete [] ptr;ptr=tmpPtr;}else{//数组原本是空的ptr=new int[1];}ptr[size++]=v;//加入新的数组元素
}CArray & CArray::operator=(const CArray &a)
{//重载赋值号的作用是使“=”左边对象里存放的数组,大小和内容都和右边的对象一样if (ptr == a.ptr){//防止a=a 这样的赋值导致出错return *this;}if (a.ptr == NULL){//如果a里面数组是空的if (ptr){delete[]ptr;}ptr = NULL;size = 0;return *this;}if (size < a.size){if (ptr){delete[] ptr;}ptr = new int[a.size];}memcpy(ptr, a.ptr, sizeof(int) * a.size);size = a.size;return *this;
}

流插入运算符的重载

cout<<5<<“this”;
cout.operator<<(5).operator<<(“this”);

假定下面程序输出为5hello,该补写什么

class CStudent
{public:int nAge;
};int main()
{CStudent s;s.nAge = 5;cout << s << "hello"<<endl;return 0;
}ostream & operator << (ostream & o,const CStudent &s)
{//使得函数在完成函数该完成的工作后返回值仍然为cout,为后续步骤作准备o<<s.nAge;return o;
}

更改输出格式

假定c是Complex复数类对象,现在希望写"cout<<c;”,就能以"a+bi"的形式输出c的值,写“cin>>c;”就能从键盘接收"a+bi"形式的输入,并且使得c.real = a;c.imag = b;

int main()
{Complex c;int n;cin >> c >> n;cout << c << "," << n;return 0;
}
//13.2+133i 87
//13.2+133i,87
class Complex
{double real, imag;
public:Complex(double r = 0, double i = 0) : real(r), imag(i){};friend ostream &operator<<(ostream &os, const Complex &c);friend istream &operator>>(istream &is, Complex &c);
};ostream &operator<<(ostream &os, const Complex &c)
{os << c.real << "+" << c.imag << "i";//a+bireturn os;
}istream &operator>>(istream &is, Complex &c)
{string s;is >> s;//将a+bi作为字符串读入,a+bi中间不能有空格int pos = s.find("+", 0);string sTmp = s.substr(0, pos);//分离出代表实部的字符串c.real = atof(sTmp.c_str());//atof库函数能够将const char*指针指向的内容转换成floatsTmp = s.substr(pos + 1, s.length() - pos - 2);//分离出代表虚部的字符串c.imag = atof(sTmp.c_str());return is;
}

重载类型转换运算符

class Complex
{double real, imag;
public:Complex(double r = 0, double i = 0) : real(r), imag(r){}operator double(){//重载强制类型转换运算符doublereturn real;}
};int main()
{Complex c(1.2, 3.4);cout << (double) c << endl;//输出1.2double n = 2 + c;//等价于double n=2+c.operator double()cout << n;//输出3.2
}

自增自减运算符重载

自增运算符++,自减运算符-- 有前置后置之分。为了区分所重载的是前置运算符还是后置运算符,c++规定:

  • 前置运算符作为一元运算符重载

    • 重载为成员函数
      T & operator++();
      T & operator–();
    • 重载为全局函数
      T1 & operator++(T2);
      T1 & operator–(T2);
  • 后置运算符作为二元运算符重载,多写一个没用的参数
    • 重载为成员函数
      T operator++(int)
      T operator–(int)
    • 重载为全局函数
      T1 operator++(T2,int );
      T1 operator–(T2,int);
int main()
{CDemo d(5);cout << (d++) << ",";//等价于 d.operator++(0)cout << d << ",";cout << (++d) << ",";//等价于 d.operator++()cout << d << endl;cout << (d--) << ",";//等价于 operator-(d,0)cout << d << ",";cout << (--d) << ",";//等价于 operator-(d)cout << d << endl;return 0;
}
//要求输出
//5 6 7 7
//7 6 5 5
class CDemo
{
private:int n;
public:CDemo(int i = 0) : n(i){}CDemo &operator++();//前置CDemo operator++(int);//后置operator int(){return n;}friend CDemo &operator--(CDemo &);//前置friend CDemo operator--(CDemo &, int);//后置
};CDemo &CDemo::operator++()
{//前置++++n;return *this;
}//++s即为:s.operator++();CDemo CDemo::operator++(int k)
{//后置++CDemo tmp(*this);//记录修改前的对象n++;return tmp;//返回修改前的对象
}//s++即为:s.operator++(0);CDemo & operator--(CDemo &d)
{//前置--d.n--;return d;
}//--s 即为:operator--(s);CDemo operator--(CDemo &d,int)
{//后置--CDemo tmp(d);d.n--;return tmp;
}//s--即为:operaor--(s,0);

继承

继承关系

class CStudent
{
private:string sName;int nAge;
public:bool IsThreeGood(){};void SetName(const string &name){sName = name;}
}class CUndergraduateStudent : public CStudent
{
private:int nDepartment;
public:bool IsThreeGood(){};//覆盖bool CanBaoYan(){};
};//派生类的写法是: 类名:public 基类名class CGraduatedStudent: public CStudent
{
private:int nDepartment;char szMentorName[20];
public:int CountSalary(){};
};
  • 继承实例程序:学籍管理

    class CStudent
    {
    private:string name;string id;char gender;//'F'女,'M'男int age;
    public:void PrintInfo();void SetInfo(const string &name_, const string &id_, int age_, char gender_);string GetName(){return name;}};class CUndergraduateStudent : public CStudent
    {//本科生类,继承了CStudent类
    private:string department;
    public:void QualifieldForBaoyan(){//给予保研资格cout << "qualified for baoyan" << endl;}void PrintInfo(){//覆盖CStudent::PrintInfo();//调用基类的PrintInfocout << "Department:" << department << endl;}void SetInfo(const string &name_, const string &id_, int age_, char gender_, const string &department_){CStudent::SetInfo(name_, id_, age_, gender_);//调用基类的SetInfodepartment = department_;}
    };int main()
    {CUndergraduateStudent s2;s2.SetInfo("Harry","11889922",19,'M',"Computer");cout<<s2.GetName()<<" ";s2.QualifieldForBaoyan();s2.PrintInfo();return 0;
    }
    

复合关系

class CMaster;class CDog
{CMaster * pm;
};class CMaster
{CDog * dogs[10];
};
class base
{int j;
public:int i;void func();
};class derived : public base
{
public:int i;void access();void func();
};void derived::access()
{j=5;//errori=5;//引用派生类的ibase::i=5;//引用基类的ifunc();//派生类的base::func();//基类的
}

保护成员protected

  • 基类的private成员:可以被下列函数访问

    • 基类的成员函数
    • 基类的友元函数
  • 基类的public成员
    • 基类的成员函数
    • 基类的友元函数
    • 派生类的成员函数
    • 派生类的友元函数
    • 其他函数
  • 基类的protected成员
    • 基类的成员函数
    • 基类的友元函数
    • 派生类的成员函数可以被当前对象的基类的保护成员
class Father
{
private:int nPrivete;
public:int nPublic;
protected:int nProtected;
};class Son:public Father
{void AccessFather(){nPublic=1;nPrivate=1;//wrongnProtected=1;Son f;f.nProtected=1;//wrong}
};

派生类的构造函数

class Bug
{
private:int nLegs;int nColor;
public:int nType;Bug(int legs, int color);void PrintBug(){};
};class FlyBug : public Bug
{int nWaings;
public:FlyBug(int legs, int color, int wings);
};Bug::Bug(int legs, int color)
{nLegs = legs;nColor = color;
}//错误写法
FlyBug::FlyBug(int legs, int color, int wings)
{nLegs=legs;//不能访问nColor=color;//不能访问
}//正确写法
FlyBug::FlyBug(int legs, int color, int wings) : Bug(legs, color)
{nWaings = wings;
}

public的赋值兼容规则

class base { };
class derived : public base { };
base b;
derived d;
  • 派生类对象可以赋值给基类对象
    b = d;
  • 派生类对象可以初始化基类引用
    base & br = d;
  • 派生类对象的地址可以赋值给基类指针
    base * pb = & d;

多态

class CBase
{
public:virtual void SomeVirtualFunction(){}
};class CDerived:public CBase
{
public:virtual void SomeVirtualFunction(){}
};int main()
{CDerived ODerived;CBase * p=&ODerived;p->SomeVirtualFunction();//调用哪个虚函数取决于p指向哪种类型的对象return 0;
}
class CBase
{
public:virtual void SomeVirtualFunction(){}
};class CDerived : public CBase
{
public:virtual void SomeVirtualFunction(){}
};int main()
{CDerived ODerived;CBase &r = &ODerived;r.SomeVirtualFunction();//调用哪个虚函数取决于r引用哪种类型的对象return 0;
}

虚函数

class base
{virtual int get();
}int base::get(){}

virtual关键字只用在类定义里的函数声明中,写函数体时不用
构造函数和静态成员函数不能是虚函数

class CBase
{
public:virtual void SomeVirtualF();
};class CDerived:public Cbase
{
public:virtual void SomeVirtualF();
}; int main()
{Cderived ODerived;CBase *p=&ODerived;p->SomeVirtualF();//调用哪个虚函数取决于p指向哪种类型的对象return 0;
}

例,输入

R 3 5
C 9
T 3 4 5
#include <iostream>
#include <stdlib.h>
#include <math.h>using namespace std;class CShape
{
public:virtual double Area() = 0;//纯虚函数virtual void PrintInfo() = 0;
};class CRectangle : public CShape
{
public:int w, h;virtual double Area(){return w * h;}virtual void PrintInfo();{cout << "Rectangle:" << Area() << endl;}
};
class CCircle : public CShape
{
public:int r;virtual double Area();{return 3.14 * r * r;}virtual void PrintInfo();{cout << "Circle:" << Area() << endl;}
};
class CTriangle : public CShape
{
public:int a, b, c;virtual double Area();{double p = (a + b + c) / 2.0;return sqrt(p * (p - a) * (p - b) * (p - c));}virtual void PrintInfo();{cout << "Triangle:" << Area() << endl;}
};
CShape *pShapes[100];
int MyCompare(const void *s1, const void *s2);int main()
{int i;int n;CRectangle *pr;CCircle *pc;CTriangle *pt;cin >> n;for (i = 0; i < n; i++){char c;cin >> c;switch (c){case 'R':pr = new CRectangle();cin >> pr->w >> pr->h;pShapes[i] = pr;break;case 'C':pc = new CCircle();cin >> pc->r;pShapes[i] = pc;break;case 'T':pt = new CTriangle();cin >> pt->a >> pt->b >> pt->c >> c;pShapes[i] = pt;break;}}qsort(pShapes, n, sizeof(CShape *), MyCompare);for (i = 0; i < n; i++){pShapes[i]->PrintInfo();}return 0;
}int MyCompare(const void *s1, const void *s2)
{double a1, a2;CShape **p1;//s1,s2是void*,不可写*s1来取得s1指向的内容CShape **p2;p1 = (CShape **) s1;//s1,s2指向pShapes数组中的元素,数组元素的类型是CShape*p2 = (CShape **) s2;//p1,p2都是指向指针的指针,类型为CShape**a1 = (*p1)->Area();//*p1的类型是CShape*,是基类指针,故此句为多态a2 = (*p2)->Area();if (a1 < a2){return -1;}else if (a2 < a1){return 1;}else{return 0;}
}

如果添加新的几何形体,比如五边形,则只需要从CShape派生出CPentagon,以及在main中的switch语句中增加一个Case,其余部分不变

class Base
{
public:void fun1()//this是基类指针,fun2()是虚函数,所以是多态{ fun2(); }virtual void fun2(){ cout << "base fun2()" << endl; }
};class Derived : public Base
{
public:virtual void fun2(){ cout << "Derived:fun2()" << endl; }
};int main()
{Derived d;Base *pBase = &d;pBase->fun1();return 0;
}
//输出Derived:fun2()

在非构造函数,非析构函数的成员函数中调用虚函数,是多态
在构造函数和析构函数中调用虚函数,不是多态
编译时即可确定,调用的函数时自己的类货基类中定义的函数,不会等到运行时才决定调用自己的还是派生类的函数
派生类中和基类中虚函数同名同参数表的函数,不加virtual也自动成为虚函数

class myclass
{
public:virtual void hello(){cout << "hello from myclass" << endl;}virtual void bye(){cout << "bye from myclass" << endl;}
};class son : public myclass
{
public:void hello(){cout << "hello from son" << endl;}son(){hello();}~son(){bye();}
};class grandson : public son
{
public:void hello(){cout << "hello from grandson" << endl;}void bye(){cout << "bye from grandon" << endl;}grandson(){cout << "constructing grandson" << endl;}~grandson(){cout << "destructing grandson" << endl;}
};int main()
{grandson gson;son *pson;pson = &gson;pson->hello();return 0;
}

虚函数表

多态实现的关键
每一个有虚函数的类(或有虚函数的类的派生类)都有一个虚函数表,该类的任何对象中放着虚函数表的指针.虚函数表中列出了该类的虚函数地址.多出来的4个字节就是用来放虚函数表的地址的

虚析构函数

通过基类的指针删除派生类对象时,通常情况下只调用基类的析构函数
但是删除一个派生类的对象时,应该先调用派生类的析构函数,然后调用基类的析构函数

解决办法:把基类的析构函数声明为virtual
派生类的析构函数可以virtual不进行声明
通过基类的指针删除派生类对象时,首先调用派生类的析构函数,然后调用基类的析构函数

一个类如果定义了虚函数,则应该将析构函数也定义成虚函数

class son
{
public:~son(){cout << "bye from son" << endl;}
};class grandson : public son
{
public:~grandson(){cout << "bye from grandson" << endl;}
};int main()
{son *pson;pson = new grandson();delete pson;return 0;
}//并没有执行~son()
class son
{
public:virtual ~son(){cout << "bye from son" << endl;}
};class grandson : public son
{
public:~grandson(){cout << "bye from grandson" << endl;}
};int main()
{son *pson;pson = new grandson();delete pson;return 0;
}//引起执行~son()

纯虚函数

没有函数体的虚函数

class A
{
private:int s;
public:virtual void Print() = 0;//纯虚函数void fun(){cout << "fun";}
};

抽象类

包含纯虚函数的类叫抽象类

  • 抽象类只能作为基类来派生新类使用,不能创建抽象类的对象

  • 抽象类的指针和引用可以指向由抽象类派生出来的类的对象

    A a;//错,A是抽象类,不能创建对象
    A* pa;//ok,可以定义抽象类的指针和引用
    pa = new A;//错误,A是抽象类,不能创建对象
    
  • 在抽象类的成员函数内可以调用纯虚函数,但是在构造函数或析构函数内部不能调用纯虚函数

  • 如果一个类从抽象类派生而来,那么当且仅当它实现了基类中的所有纯虚函数,他才能成为非抽象类

输入输出

输出重定向

#include <iostream>
using namespace std;
int main()
{int x, y;cin >> x >> y;freopen("test.txt", "w", stdout);if (y == 0){cerr << "error." << endl;}else{cout << x / y;}return 0;
}

istream类

istream & getline(char * buf,int bufsize);
istream & getline(char * buf,int bufsize,char delim);
读入一行或先到bufsize,或读到delim为止,哪个先到算哪个

bool eof();判断输入流是否结束
int peek();返回下一个字符,但不从流中去掉

流操作算子

需要#include <iomanip>

整数流的基数

int n = 10;
cout << n << endl;
cout << hex << n << endl;
cout << dec << n <<endl;
cout << oct << n << endl;

控制浮点数精度的流操作算子

成员函数cout.precision(5);
流操作算子cout<< setprecision(5);可以连续输出
指定输出浮点数的有效位数,非定点方式输出时
指定输出小数点后的有效位数,定点方式输出时
setiosflags(ios::fixed);以小数点位置固定的方式输出
resetiosflags(ios::flags);取消

设置域宽的流操作算子

int w = 4;
char string[10];
cin.width(5);
while (cin >> string)
{cout.width(w++);cout << string << endl;cin.width(5);;
}
输入1234567890
输出
1234567890

综合

double x = 1234567.89;
double y = 12.34567;cout << setprecision(5) << x << y << endl;
1.2346e+006 12.346//保留5位有效数字
cout << fixed <<setprecision(5) << x << << y << endl;
1234567.89000 12.34567//保留小数点后面5位
cout << scientific << setprecision(5) << x << y << endl;
1.23457e+006 1.23457e+001//科学计数法输出,且保留小数点后5位
cout << showpos << fixed << setw(12) << setfill('*') << 12.1 << endl;
***+12.10000//非负数要显示正好,输出宽度为12字符,宽度不足则用*替代
cout << noshowpos << setw(12) << left << 12.1 << endl;
12.10000****//非负数不显示正号,输出宽度为12字符,宽度不足则右边用填充字符填充
cout << setw(12) << right << 12.1 << endl;
****12.10000//输出宽度为12字符,宽度不足则左边用填充字符填充
cout << setw(12) << internal << -12.1 << endl;
-***12.10000//宽度不足时,负号和数值分列左右,中间用填充字符填充

用户自定义流操作算子

ostream &tab (ostream &output)
{return output << '\t';
}
cout << "a" << tab << 'b' << endl;
//a    b

文件读写

is 派生出 istream 和 ostream
istream 派生出 ifstream 和 iostream
ostream 派生出 iostream 和 ofstream
iostream派生出fstream

创建文件

#include <fstream>
ofstream outFile("clients.dat", ios::out|ios::binary);//创建文件
ios::out文件打开方式ios::out 输出到文件,删除原有内容ios::app 输出到文件,保留原有内容,总是在尾部添加
ios::binary 以二进制文件格式打开文件
ofstream fout;
fout.open("test.out", ios::out|ios::binary);
也可以先创建ofstream对象,再用open函数打开
if (!fout)
{cout << "File open error!" << endl;
}判断是否打开成功

文件读写指针

ofstream fout("a1.out", ios::app);//以添加方式打开
long location = fout.tellp();//取得写指针的位置
location = 10;
fout.seekp(location);//将写指针移动到第10个字节处
fout.seekp(location,ios::beg);//从头数location
fout.seekp(location,ios::cur);//从当前位置数location
fout.seekp(location,ios::end);//从尾部数location
location可以为负值
ifstream fin("a1.in", ios::ate);//打开文件,定位文件指针到文件尾
long location = fin.tellg();//取得读指针的位置
location = 10L;
fin.seekg(location);//将读指针移动到第10个字节处
fin.seekg(location,ios::beg);//从头数location
fin.seekg(location,ios::cur);//从当前位置数location
fin::seek(location,ios::end);//从尾部数location

字符文件读写

#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>using namespace std;int main()
{vector<int> v;ifstream srcFile("in.txt", ios::in);ofstream destFile("out.txt", ios::out);int x;while (srcFile >> x){v.push_back(x);}sort(v.begin(), v.end());for (int i = 0; i < v.size(); i++){destFile << v[i] << " ";}destFile.close();srcFile.close();return 0;
}

二进制文件读写

二进制读文件

ifstream 和 fstream 的成员函数
istream & read (char *s,long n);

二进制写文件

ofstream 和 fstream的成员函数
istream & write (const char *s,long n);

在文件中写入和读取一个整数

#include <iostream>
#include <fstream>using namespace std;int main()
{ofstream fout("some,dat", ios::out | ios::binary);int x = 120;fout.write((const char*)(&x), sizeof(int));fout.close();ifstream fin("some.dat", ios::in | ios::binary);int y;fin.read((char*)&y, sizeof(int));fin.close();cout << y << endl;return 0;
}

从键盘输入几个学生的性名和成绩,并以二进制文件形式保存

#include <iostream>
#include <fstream>using namespace std;struct Student
{char name[20];int score;
};int main()
{Student s;ofstream OutFile("students.dat", ios::out | ios::binary);while (cin >> s.name >> s.score){OutFile.write((char *) &s, sizeof(s));}OutFile.close();return 0;
}

读取此文件

#include <iostream>
#include <fstream>using namespace std;struct Student
{char name[20];int score;
};int main()
{Student s;ifstream inFile("students.dat", ios::in | ios::binary);if (!inFile){cout << "error" << endl;return 0;}while (inFile.read((char *) &s, sizeof(s))){int readedBytes = inFile.gcount();//看刚才读了多少字节cout << s.name << " " << s.score << endl;}inFile.close();return 0;
}
/*输入:
Tom 60
Jack 80
Jane 40*/

将Jane改为Mike

#include <iostream>
#include <fstream>
#include <cstring>using namespace std;struct Student
{char name[20];int score;
};int main()
{Student s;fstream iofile("students.dat", ios::in | ios::out | ios::binary);if (!iofile){cout << "error!";return 0;}iofile.seekp(2 * sizeof(s), ios::beg);//定位写指针到第三个记录iofile.write("Mike", strlen("Mike") + 1);iofile.seekg(0, ios::beg);//定位读指针到开头while (iofile.read((char *) &s, sizeof(s))){cout << s.name << " " << s.score << endl;}iofile.close();return 0;
}

文件拷贝

将src.dat 拷贝到 dest.dat

#include <iostream>
#include <fstream>using namespace std;int main(int argc, char *argv[])
{if (argc != 3){cout << "File name missing!" << endl;return 0;}ifstream inFile(argv[1], ios::binary | ios::in);//打开文件用于读if (!inFile){cout << "Source file open erroe." << endl;return 0;}ofstream outFile(argv[2], ios::binary | ios::out);//打开文件用于写if (!outFile){cout << "New file open error." << endl;inFile.close();//打开的文件一定要关闭return 0;}char c;while (inFile.get(c)){outFile.put(c);}outFile.close();inFile.close();return 0;
}

模板

模板函数

void swap(int &x, int &y)
{int temp = x;x = y;y = temp;
}void swap(double &x, double &y)
{double temp = x;x = y;y = temp;
}

将此两个函数化为一类模版函数,用函数模版解决

template <class 类型参数1,class 类型参数2,...>
返回值类型 模版名(形参表)
{函数体
};template <class T>
void swap(T &x,T &y)
{T temp = x;x = y;y = temp;
}
int main()
{int n = 1,m = 2;swap(n,m);//编译器自动生成void swap(int &,int &)函数double f = 1.2,g = 2.3;swap(f,g);//编译器自动生成void swap(double &,double &)函数return 0;
}

多个类型参数

template <class T1, class T2>
T2 print(T1 arg1,T2 arg2)
{cout << arg1 << " " << arg2 << endl;return arg2;
}

求数组最大元素的MaxElement函数模版

template<class T>
T MaxElement(T a[], int size)
{T tmpMax = a[0];for (int i = 1; i < size; i++){if (tmpMax < a[i]){tmpMax = a[i];}}return tmpMax;
}

不通过参数实例化函数模版

template<class T>
T Inc(T n)
{return 1 + n;
}int main()
{cout << Inc<double>(4) / 2;//输出2.5return 0;
}

函数模版重载

只要他们的形参表或类型参数表不用即可

template <class T1,class T2>
void print(T1 arg1, T2 arg2)
{cout << arg1 << arg2 << endl;
}template <class T>
void print(T arg1, T arg2)
{cout << arg1 << arg2 << endl;
}template <class T, class T2>
void print(T arg1, T arg2)
{cout << arg1 << agr2 << endl;
}

匹配模版函数时,不进行类型自动转化

template <class T>
T my(T arg1, T arg2)
{cout << arg1 << " " << arg2 << endl;return arg1;
}
my(5,7);//int int
my(5.8,8.4);//double double
my(5,8.4);//报错

函数模版和函数次序

  1. 先找参数完全匹配的普通函数(非由模版实例化而得到的函数)
  2. 再找参数完全匹配的模版函数
  3. 再找实参数经过自动类型转换后能够匹配的普通函数
  4. 报错

类模版

template <class 类型参数1, class 类型参数2, ...>
class 类模板名
{成员函数和成员变量
};
template <typename 类型参数1, 类型参数2, ...>
class 类模板名
{成员函数和成员变量
};
template<class T1, class T2>
class Pair
{public:T1 key;//关键字T2 value;//值Pair(T1 k, T2 v) : key(k), value(v){};bool operator<(const Pair<T1, T2> &p) const;
};template<class T1, class T2>
bool Pair<T1, T2>::operator<(const Pair<T1, T2> &p) const
{//Pair的成员函数operator <return key < p.key;
}int main()
{Pair<string, int> student("Tom", 19);//实例化出一个类Pair(string,int>cout << student.key << " " << student.value;return 0;
}

编译器由类模板生成类的过程叫类模板的实例化

函数模板作为类模板成员

template<class T>
class A
{public:template<class T2>void Func(T2 t){//成员函数模板cout << t << endl;}
};int main()
{A<int> a;a.Func('K');//成员函数被实例化a.Func("hello");//成员函数再次被实例化return 0;
}

类模板与非类型参数

类模板的<类型参数表>中可以出现非类型参数

template<class T, int size>
class CArray
{T array[size];
public:void Print(){for (int i = 0; i < size; i++){cout << array[i] << endl;}}
};CArray<double, 40> a2;
CArray<int, 50> a3;//a2,a3属于不同的类

类模板与继承

  • 类模板从类模板派生

    template<class T1, class T2>
    class A
    {T1 v1;T2 v2;
    };template<class T1, class T2>
    class B : public A<T2, T1>
    {T1 v3;T2 v4;
    };template<class T>
    class C : public B<T, T>
    {T v5;
    };int main()
    {B<int, double> obj1;C<int> obj2;return 0;
    }
    //相当于
    class B<int, double> : public A<double, int>
    {int v3;double v4;
    };class A<double, int>
    {double v1;int v2;
    };
    
  • 类模板从模板类派生

    template<class T1, class T2>
    class A
    {T1 v1;T2 v2;
    };template<class T>
    class B : public A<int, double>
    {T v;
    };int main()
    {B<char> obj1;//自动生成两个模板类A<int, double>和B<char>return 0;
    }
    
  • 类模板从普通类派生

    class A
    {int v1;
    };
    template <class T>
    class B:public A
    {//所有从B实例化得到的类,都以A为基类T v;
    };
    int main()
    {B<char> obj1;return 0;
    }
    
  • 普通类从模板类派生

    template <class T>
    class A
    {T v1;int n;
    };
    class B:public A<int>
    {double v;
    };
    int main()
    {B obj1;return 0;
    }
    

类模板与友元

  • 函数,类,类的成员函数作为类模板的友元

    void Func1()
    {}class A
    {};class B
    {public:void Func(){}
    };template<class T>
    class Tmpl
    {friend void Func1();friend class A;friend void B::Func();
    };//任何从Tmp1实例化来的类,都有以上三个友元
    
  • 函数模板作为类模板的友元

    template<class T1, class T2>
    class Pair
    {private:T1 key;T2 value;
    public:Pair(T1 k, T2 v) : key(k), value(v){};bool operator<(const Pair<T1, T2> &p) const;template<class T3, class T4>friend ostream &operator<<(ostream &o, const Pair<T3, T4> &p);
    };
    template<class T1,class T2>
    bool Pair<T1,T2>::operator<(const Pair<T1,T2>&p)const
    {//小的意思就是关键字小return key < p.key;
    }
    template <class T1,class T2>
    ostream & operator << (ostream &o, const Pair<T1,T2> &p)
    {o << "(" << p.key << "," << p.value << ")";return o;
    }
    int main()
    {Pair<string,int> student("Tom",29);Pair<int,double>obj(12,3.14);cout << student << " " << obj;return 0;
    }
    
  • 函数模板作为类的友元

    class A
    {int v;
    public:A(int n) : v(n){}template<class T>friend void Print(const T &p);
    };template<class T>
    void Print(const T &p)
    {cout << p.v;
    }int main()
    {A a(4);Print(a);return 0;
    }
    /*所有从template<class T>
    friend void Print(const T &p);生成的函数,都成为A的友元
    但是自己写的函数void Print(const T &p){}
    不会称为A的友元*/
    
  • 类模板作为类模板的友元

    template<class T>
    class B
    {T v;
    public:B(T n) : v(n){}template<class T2>friendclass A;
    };template<class T>
    class A
    {public:void Func(){B<int> o(10);cout << o.v << endl;}
    };int main()
    {A<double> a;a.Func();return 0;
    }
    /* A<double>类,成了B<int>类的友元
    任何从A模板实例化出来的类,都是任何B实例化出来的类的友元*/
    

    类模板与静态成员变量

    类模板中可以定义静态成员,那么从该类模板实例化得到的所有类,都包含同样的静态成员

    template<class T>
    class A
    {private:static int count;
    public:A(){ count++; }~A(){ count--; }A(A &){ count++; }static void PrintCount(){ cout << count << endl; }
    };template<> int A<int>::count = 0;
    template<> int A<double>::count = 0;int main()
    {A<int> ia;A<double> da;ia.PrintCount();da.PrintCount();return 0;
    }
    

String类

  • 子串,成员函数substr
    s2 = s1.substr(4,5);//从下标4开始5个字符
  • size();length()字符串长度
  • 寻找string 中的字符
    s1.find(“lo”);在s1中从前往后找lo第一次出现的地方
    s1.find(“lo”,2);从下标为2的地方往后找
  • s1.rfind(“lo”);倒着查找
  • find_first_of
    s1.find_first_of(“abcd”);在s1中从前往后找abcd中任何一个字符第一次出现的地方
  • find_last_of
    s1.find_last_of(“abcd”);在s1中从前往后找abcd中任何一个字符最后一次出现的地方
  • find_first_not_of
    s1.find_first_not_of(“abcd”);在s1中从前往后找不在abcd中任何一个字符第一次出现的地方
  • find_last_not_of
    s1.find_last_not_of(“abcd”);在s1中从前往后找不在abcd中任何一个字符最后一次出现的地方
  • erase()
    s1.erase(5);去掉下标5及之后的字符
  • replace()
    s1.replace(2,3, “haha”);将s1中下标2开始的三个字符换成 “haha”
    s1.replace(2,3, “haha”,1,2);将s1中下标2开始的三个字符换成 “haha”中下标1开始的2个字符
  • insert()
    s1.insert(5,s2);将s2插入s1下标5的位置
    s1.insert(2,s2,5,3);将s2中下标5开始的3个字符插入s1下标2的位置
  • c_str()
    s1.c_str()转换为char *形式

函数对象

若一个类重载了运算符“()”,则该类的对象就成为函数对象

class CMyAverage
{//函数对象类
public:double operator()(int a1, int a2, int a3){return (double) (a1 + a2 + a3) / 3;}
};CMyAverage average;//函数对象
cout << average(3,2,3);//average.operator()(3,2,3)

程序设计与算法郭炜老师的课堂笔记3相关推荐

  1. 程序设计与算法郭炜老师的课堂笔记2

    程序设计与算法郭炜老师的课堂笔记2 枚举 完美立方 生理周期 称硬币 熄灯问题 递归 求阶乘 汉诺塔 N皇后 逆波兰表达式 表达式求值 上台阶 放苹果 算24 二分算法 找一对数 分治 归并排序 快速 ...

  2. 程序设计与算法郭炜老师的课堂笔记1

    程序设计与算法郭炜老师的课堂笔记1 基础 与或非 位运算 字符串操作库函数 strtok尝试 字符串0新认识 void 指针无定义 快排 变量 排序 Vector vector示例 用**vector ...

  3. xmuoj《C++与Python语法入门练习(By郭炜老师)》python参考代码

    目录 前言 xmuoj对应链接 Pycharm安装 代码 001 输出第二个整数 002 字符菱形 003 打印ASCII码 004 打印字符 005 整型数据类型存储空间大小 006 浮点型数据类型 ...

  4. AI公开课:19.04.17杨松帆—好未来AI Lab负责人《为人工智能时代打造一个AI老师》课堂笔记以及个人感悟

    AI公开课:19.04.17杨松帆-好未来AI Lab负责人<为人工智能时代打造一个AI老师>课堂笔记以及个人感悟 导读 杨松帆,现为好未来教育集团人工智能实验室负责人.曾任FaceThi ...

  5. 郭炜老师 程序设计与算法(二) 枚举

    枚举:(一)完美立方 题目: 题解: 设计abcd四个属性的遍历范围和遍历顺序,使用for循环进行遍历 总结: 枚举需要注意枚举的范围,一般只需要给出一个大致范围,不需要十分精确的计算 枚举还需要注意 ...

  6. 例题代码|程序设计与算法(二) 算法基础 北大 郭炜 中国大学MOOC 笔记

    网站链接

  7. 郭炜老师魔兽三备战思路及代码

    这个题目,我花了差不多4天才全部做出来的,一开始也没很好的设计,就是看见有什么功能,就写一个函数的声明,然后写另一个类的时候,发现我要用到前面写的类的函数时,才去定义相应的函数,所以可能会很混乱.我写 ...

  8. 北大郭炜《程序设计与算法(三)》Mooc笔记:运算符重载和继承

    文章目录 运算符重载 运算符重载的基本概念 赋值运算符的重载 为什么引入赋值运算符重载? 浅拷贝和深拷贝 对operator=返回值的讨论 复制构造函数的相同困境 运算符重载为友元函数 实例:可变长数 ...

  9. Python1.语言基本要素上(郭炜老师python大学mooc)

    首先是 程序中的所有字符都必须是英文字符,不能是中文的全角字符, 除非输出中文,才会在"字符串"中使用中文 目录 一.注释 二.变量 三.赋值语句 四.字符串初步 五.字符串和数的 ...

最新文章

  1. qt mysql now()_Qt + mysql 運用 (項目一)
  2. CXF 调用 .net webservice
  3. python创建csv文件并写入-Python 读写 CSV
  4. mysql的最佳索引攻略
  5. c++代码表白_推荐!在浪漫的日子里程序猿如何用C语言实现520表白代码
  6. Java中的do-while循环——通过示例学习Java编程(11)
  7. JavaScript 函数定义+内置函数使用+array对象+object类型
  8. 【华为云技术分享】Linux内核模块依赖图绘制(2)
  9. spring高级之AOP详解
  10. 腾讯这套SpringMvc面试题你了解多少?(面试必备)
  11. 快速入门Web前端开发的正确姿势
  12. C#不登录电脑启动程序
  13. sed截取连接数升高时的慢日志
  14. 监控程序日志并发送警告邮件
  15. CSRF跨站请求伪造漏洞
  16. mysql计算同比和环比的区别_【面试真题】Mysql实现计算同比、环比
  17. 最适合十二星座的那些表白方式!
  18. 当C++遇到空指针异常......
  19. EC200 EC600 EC20接入工业互联网云平台
  20. C语言字符串去空格(最简单版本)

热门文章

  1. 正则“^[a-zA-Z]” 和 “[^a-zA-Z]”的区别
  2. 火山引擎云原生大数据在金融行业的实践
  3. 新版正方教务系统Java爬取_正方教务系统成绩爬取(仅个人)+tk可视化
  4. 超图举例单值专题图色带样式控制
  5. 关于WPS Office安全漏洞情况的通报
  6. 爬有道在线翻译(已完善)
  7. 苹果痛下狠手,全面禁止App内部的广告拦截软件
  8. 高性能零售IT系统的建设04-APM全链路建设精讲
  9. 通信电子电路(一)通电课程背景 以及选频网络概念
  10. 梦幻西游进入游戏显示服务器程序停止工作,win10系统提示“梦幻西游已停止工作”的设置教程...