简介
此篇章为C++收尾篇。考前放出
C++
OOP基础功能收尾
- 对象赋值 ObjectName_2 = ObjectName_1; 数据成员直B接拷贝,成员函数为拷贝,这是因为在声明类的时候成员函数就初始化在堆中了。
- Box(int=10,int=10,int=10); int Box(int h, int w, int len){} 在初始声明时就可以给与默认值
- 对象拷贝
ClassName ObjectName(ObjectName1);
将类的对象类似值传递没有加&给函数时,也是一种对象的拷贝
把对象作为返回值,也是一种拷贝 - Copy Constructor
ClassName ObjectName2(ClassName & ObjectName1)
拷贝构造函数的第一个参数必须是一个对象的引用,用于调用原型对象,所以有“拷贝”之称。
int Box::Box(const Box &temObj){ height = temObj.height + 1}
运算符重载
- 用成员函数实现复数加法:Complex c3 = c1.add(c2); 这是使用拷贝函数的方法。
假若使用运算符重载,则如
class Complex
{
public:
Complex operator+(Complex &c2);
private:
double real;
double imag;
}
Complex Complex::add(Complex &c2)
{
return Complex(real+c2.real, imag+c2.imag) //运算符重载在c1中,real和imag是c1的
}
- operator加运算符代表运算符重载,此时 c1.operator(c2) 和 c1+c2 是等价的
- 运输符重载的限制:五个运算符不能重载,重载不能改变运算符的一些性质,不能有运算符(即必须有参数),参数至少有一个是类的对象或其引用。
- 使用运输符重载用成员函数或友元函数,以便访问里面private的数据,如c1的实部和虚部 运算符重载友元函数定义方式: Complex operator + (Complex &c1, double &d){…}
- 当前对象在重载中时,默认有 operator + (Complex *this, Complex &c2) 此时不能 Complex c3 = 3.14 + c2;
- 运算符重载友元函数定义方式: Complex operator + (Complex &c1, double &d){…} 声明方式 friend Complex operator + ()
注意由于不是成员函数,所以参数没有默认 *this,要自己加。然而这更加灵活,可以 Complex operator + (double &d, Complex &c1){…} 这样就可以实现 Complex c3 = 3.14 + c2;
所以双目运算符用友元函数做重载比较好。重载>,<,==的时候不妨使用friend bool operator … - 重载单目运算符
++前置和后置 使用operator( ); 是前置 operator(int) 是后置
Time Time::operator++(){
sec++;
return *this;
}
Time Time::operator++(int){
Time temp(*this);
sec++
return temp;
}
- ”>>“和“<<”只能用友元函数作为运算符重载,函数的类型(返回值)和第一个参数都是 &ostream 和 &istream
public:
friend ostream& oprator >> (ostream&,Complex&);
ostream& operator >> (ostream& output, Complex& c){
cout << c.real << "+" << c.imag << "i";
}
- 一点细节:用引用作为形参,减少时间空间开销;返回值是引用时,返回的不是常量,是引用所代表的对象。类里面的指针成员不是太好直接拷贝,所以尽量自己设计拷贝构造函数,给它开空间。
转换构造函数和类型转换函数
- 转换函数可以将一个指定类型的数据转换为类的对象,但是不能把一个类的对象转换为数据。
- operator 类型名( ) 没有参数 返回值的类型由类型名指定
继承与派生
-
单继承,派生类只从一个基类派生。多继承,派生类从多个类中派生。
-
派生类的声明方式:
class Student1: public Student{...};
继承有public,private,protected一样。默认私有继承。 -
简单派生类的构造函数,冒号代表要调用原构造函数传递参数。假若不传递参数给基类构造函数,则不需要冒号加东西。系统会自动调用基类的默认构造函数。基类构造函数若有没有默认值的参数
public: Student1(int n,string nam, char s, int a, string ad): Student(n,nam,s) {age = a; addr=ad;} 派生类构造函数名(总形式参数表列) :基类构造函数名(实际参数表类)
-
一般来说可以只在类体中写声明,声明不必加冒号。外部再
Student1::Student1(...)
也可以使用初始化表Student1(int n,string nam, char s, int a, string ad): Student(n,nam,s), age(a), addr(ad){}
-
基类构造函数->派生类构造函数->派生类析构函数->基类析构函数
-
有子对象的派生类的构造函数
-
Student1(int n,string nam, int n1, string nam1): Student(n,nam), monitor(n1,nam1){} //调用子对象构造函数了个monitor
-
多层派生的时候的构造函数只用调用上一层构造函数
Student2(...): Student1(...){...}
-
派生类成员的访问权限
(1)派生类和基类自己访问自己的成员 一切均可
(2)派生类外访问基类和派生类的成员 private, protected不可,public可
(3)基类访问派生类成员 不可能
(4)派生类访问基类成员- public公有继承 所有成员仍为原有权限
- private私有继承 所有成员全部为私有权限
- protected保护继承 私有仍为私有 公有和保护为保护
-
保护成员不能被外界引用,但是可以被派生类使用。即可以在派生类中添加方法调用基类中的成员,而不必使用基类中的方法。
我想要一些成员只应用在一套类的体系中,而不被外部访问时,就有protected。不需要再往下继承的类的功能可用private把它隐蔽起来。
继承上,公有继承保护性不变,保护继承稍微保护了原来的public,私有继承全部保护了。常用公有继承。 -
多重继承
优点:自然做到对单继承的扩展,可以继承多个类的功能
缺点:结果复杂化,优先顺序模糊,功能冲突。
声明方法:class D: public A, private B, protected C {...}
-
二义性:很多时候多个基类都有名字相同的成员,调用时要加命名域。
如c1.A::a=3; c1.A::display();
c1为A类派生类C的实例
同名覆盖:基类的同名成员在派生类中被屏蔽,成为"不可见"的。
若是方法,参数列表一样则覆盖,参数列表不一样则重载。 -
虚基类:A和B同继承自基类N,C继承自A和B。C++提供虚基类,在间接继承同一份基类时只保留一份成员
声明方法:class A{...}; class B : virtual public A {...}; class C : virtual public A {...}; class D: public B, public C {...}; //虚基类的初始化还要特别针对原基类进行初始化
-
基类与派生类的转换
不同类型数据之间的自动转换和赋值,称为赋值兼容。
只有共用派生类才是基类真正的子类型,它完整地继承了基类的功能
派生类对象可以向基类对象赋值。1.赋值时舍弃派生类自己的成员2.赋值只是对数据成员赋值,对成员函数不存在赋值问题3.仍不能通过基类访问派生类新增的成员。 -
可以通过派生类对象对基类进行赋值或初始化
class A{...}; class B: public A {...} ; A a1; B b1 A& r1=a1; //r1是引用别名 A& r2=b1; //指向与a1起始位置相同的地址的指针 //基类的指针可以指向派生类,反过来不可以
这意味着派生类对象可以被当作基类来引用,做为别的函数的参数
-
类的组合:类中的成员数据是另一个类的对象。如Line里有两个Dot的对象。
多态性
-
多态性:一个接口,多种方法
-
重载参数不一样,是静态多态,是编译时的多态性。而函数覆盖(虚函数)是针对每个类不一样,称为动态多态,是运行时的多态性。
-
虚函数函数覆盖:
- virtual声明成员函数为虚函数,可以在派生类中重新定义此函数。在类外定义虚函数时,不需要再加virtual。
- 派生类中重新定义此函数。要求函数名、函数类型、函数参数个数和类型全部与基函数相同。当一个成员函数被声明为虚函数后,派生类中同名函数(重载函数)也自动成为虚函数。但最好再加个virtual。若派生类中没有对基类虚函数重新定义,则直接继承基类虚函数。
- 定义一个指向基类对象的指针变量或引用变量,并使它指向同一类族中需要调用该函数的对象
- 通过指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。
Cylinder Cy1; Cylinder *p = Cy1; Cycle *q = Cy1; 在虚函数的情况下 q->area()调用Cylinder的area函数。 非虚函数的情况下 q->area()调用基类(Cycle)的area函数。 p->area()都是调用Cylinder的area函数
-
使用方法
先定义虚函数,然后定义指向基类的指针,用这个指针就可以不停地指向同一类族中不同对象的同名函数 -
假若定义的是非虚函数,则用基类指针调用成员函数,系统调用基类成员函数。假如使用派生类指针调用该成员函数,则系统会调用派生类对象中的成员函数。 简而言之,虚函数同意了派生类函数对基类函数的抹消。
-
理解关联(指针)、静态关联(重载、通过对象名调用的虚函数)、动态关联(基类指针调用虚函数)
-
排他性:
一个成员函数被声明为虚函数后,同一类族中的类就不能再定义非virtual的非重载同名函数了。默认其为virtual,会直接函数覆盖。 -
何时使用虚函数:
1.功能是否可能被更改且与参数无关,比如打印area,把函数声明为虚函数。
2.成员函数如果是通过基类的指针或引用去访问,则应当声明为虚函数
3. 有时候基类并不定义其函数体,功能等待派生类去添加,则virtual一个虚函数名。这称为纯虚函数。表现了基类定义接口,基类固定了函数名称、参数、返回类型。 -
虚析构函数:用指针销毁派生类的时候,假如是非虚析构函数,则会依照指针类型销毁,造成内存泄漏。所以通过声明虚析构函数,用任意类型指针销毁派生类都不会出现问题。
-
纯虚函数
virtual float area() const = 0; // 纯虚函数 也可以不const直接 = 0; virtual float area() const {return 0}; // 虚函数,有{...} virtual 函数类型 函数名 (参数表类) = 0;
-
抽象类
有一些类不是用来生成对象的,只是单纯用来定义一些基本类型用作继承。通常称为抽象基类。如果在派生类中没有对所有纯虚函数进行定义,则此派生类仍然是抽象类,不能用来定义对象。
输入输出流
基本概念
- 对系统指定的标准设备的输入输出—— 标准I/O
对外存磁盘为对象的输入输出——文件I/O
对内存中指定的空间进行的输入输出——串I/O - 流对象cin、cout和流运算符的定义存放在C++的输入输出流库中
- 内存中为每一个数据流开辟一个内存缓冲区,缓冲区中的数据就是流
- C++中输入输出流被定义为类,C++中I/O库中的类称为流类
- 类库有关的一些头文件:
- fstream 用于文件I/O
- strstream 用于字符串流I/O
- iomanip 用于格式化I/O
- iostream定义的流对象
- cin——从标准输入设备(键盘)输入到内存的数据流对象
- cout——从内存数据输出到标准输出设备(屏幕)
- cerr——输出错误
- clog——输出调试
- iostream定义的重载运算符
<<和>>针对后面的变量类型进行了重载。 - 输入输出重定向及其应用(相当于重定义cin,cout的流对象)
注意,fopen等是C语言的函数,返回值为指向文件的指针。使用“文件句柄=fopen”去接收指针。FILE *a = NULL; FILE *b = NULL; //初始化文件指针(文件句柄) a = fopen("input.txt","r"); //打开文件,打开方式为输入,用文件句柄a表示 b = fopen("output.txt","w"); //打开文件,打开方式为输出,用文件句柄b表示 ch = get(a); put(b); freopen("input.txt",'r',stdin); //输入重定向 freopen("output.txt",'w',stdout); //输出重定向 fclose(stdout); //关闭重定向输出文件,标准输出还原为屏幕 fclose(stdin); //关闭重定向输入文件,标准输入还原为键盘 fclose(a); fclose(b);
而下文标准输入输出流是C++的函数,ifstream和ofstream是类,使用“类名 流对象名” 创建流对象。
标准输出流
- cout (console output 控制台输出) 向标准输出设备输出
可以重定向输出到磁盘文件 - cerr 标准错误流,像标准错误设备输出有关错误信息。
cerr流中的信息只能在显示器输出 - clog 流对象也是标准错误流。
clog将信息存放在在缓冲区中,缓冲区满后或遇到endl时向显示器输出。 - 控制符控制输出格式
- ostream的一些成员函数
cout.put('a'); //输出单个字符 cout.put(71).put(79).put(68); //连续输出单个字符
标准输入流
- cin (console input 控制台输入)
程序遍历通过流提取符">>"从cin流中提取对象
">>"从流中提取数据常常连续获得空格、\n、\t。直到打下回车才结束 - istrean的一些成员函数
文件的读写
- ASCII文件(纯文本文件) .txt, .cpp, .doc 与cout看起来一样的文件
- 二进制文件:.exe, 与内存中看起来一样的文件
- 文件流是以外存文件为输入输出对象的数据流
每一个文件流都有一个内存缓冲区与之对应
cout、cin 已经定义好了流对象
infile、outfile 需要自定义对象 - 打开磁盘文件,就是将流对象和外部存储的文件关联起来
ofstream outfile; //定义输出文件流类对象 outfile.open("f1.dat", ios::out); \\文件流对象.open(磁盘文件名,输入输出方式); ofstream outfile("c:\new\f1.dat",ios::out); \\文件流对象(磁盘文件名,输入输出方式); \\输入输出方式 ios::in, ios::out, ios::app, ios::ate \\打开的文件存在与否ios::trunc, ios::nocreate, ios::noreplace \\使用位或运算符|组合输入输出方式 ios::in | ios::out;
- 关闭文件
outfile.close(); //终止对outfile这个流对象的操作。
- 对ASCII文件的操作
- 以文件句柄为对象名,可以用已经重载运算符的"<<"">>"输入输出标准类型的数据
- 以文件句柄为对象名,可以用iostream的put,get,getline等成员函数进行字符的输入输出
\\输出文件 oftstream out("f1.dat",ios::out); if(!out) { cerr<<"open error"<<endl; exit(1); } for(int i=0;i<10;i++) { cin>>a[i]; out<<a[i]<<" "; } out.close; \\输入文件 iftstream in("f1.dat",ios::in); if(!in) { cerr<<"open error"<<endl; exit(1); } for(int i=0;i<10;i++) { cin>>a[i]; cout<<a[i]<<" "; } in.close; \\fstream file("f1.dat",ios::out|ios::in); 既可以读又可以写 while(in.get(ch)); //一个一个读取 cout<<ch;
- 对二进制文件的操作
istream& read(char *buffer, int len); ostream& write(const char *buffer, int len); \\char *buffer指向内存中的一段存储控件。char是最小指针故用char(实际unsign char)。len是字符串 调用方式 ofstream a("file1.dat", ios::binary); //可以 | ios::in | ios::out ifstream b("file2.dat", ios::binary); a.write(p1,50); b.read(p2,30); \\一些输出方式 for(int i =0;i<3;i++) a.write((char*)&stud[i],sizeof(stud[i])); a.close; \\一些输入方式 for(int i = 0;i<3;i++) b.read((char*)&stud[i],sizeof(stud[i])); b.close;
- 二进制文件可以使用随机读写,可以使用文件指针(也是用创建的流对象名)移动到文件里的指定位置
定义时可以用ios::beg等来定义位置 file.seekg(3); 后移动3位
字符串流
- 字符串流也叫做缓冲区
- 无了