C++基础
面向对象
四大特性
1.1 抽象与分类 分类所依据的原则---抽象 抽象出同一类对象的共同属性和行为形成类。
1.2 封装 隐藏对象的内部细节,只保留有限的对外接口。
1.3 继承 其意义在于软件的复用,改造,扩展已有类形成新的类。
1.4 多态 同样的消息作用于不同的对象上有可能引起不同的行为。基本数据类型
整数类型(int short long longlong),字符类型(char 128 ascill码),布尔类型(bool),实数类型(浮点数:单精度,双精度,扩展精度)
字符串类型(有字符串常量,但基本类型中没有字符串变量,一般采用字符数组存储字符串)
初始化方式:int a=0; int a(0);int a{0}; int a={0};
符号常量:两种定义形式(等价) 定义时一定要初始化(之后不在改变)
const float pi=3.14159;
float const pi=3.14159;
算术运算符
复合运算符:+=,-=,/=,*=,%=,<<=,>>=,^=,!=,|=。
逻辑运算符:(高到低) !,&&,||
条件运算符:a>b?a:b;
sizeof运算符:计算类型或变量所占用的字节数。
位运算:按位与(置0),按位或(置1),异或(指定位翻转)
运算优先级及类型转换:(低-->高) char short int long longlong float double
隐式转换:低优先级向高优先级转换。
显式转换:long(int a) (long) int a;static_cast<int>z(常用);
const_cast dynamic_cast reinterpret_cast static_cast</int>
相关语句
switch(day){ case 1: break; case 2: break; ... default: break; } do{ } while(i<=10);
类型别名: typedef double d; using d=do
自定义类型:
enum weekday {SUN,MON,TUE,WED,THR,FRI,SAT};
默认情况下值为:分别对应{0,1,2,3,4,5,6};
enum weekday {SUN=7,MON=1,TUE,WED,THR,FRI,SAT}; (7,1,2,3,4,5,6)
函数
函数传递:实参可以是常量,变量或表达式,类型必须与形参相同。
值传递:传递参数值,即单向传递。
引用传递:实现参数的双向传递。(常引用作参数可以保证实参数据的安全)。
内联函数(inline):编译时,在调用处用函数体进行替换,节省了参数传递,控制转移等开销。
内联函数体内不能有循环语句和switch语句。
内联函数的定义必须在内联函数第一次被调用之前。
对其不能进行异常接口声明。
类与对象
基本特点:抽象,封装继承与多态。
抽象:数据抽象:抽象类的属性与状态。函数抽象:抽象某类对象的共有行为特征或具体功能。
类的设计:
此类型应该有怎样的函数与操作符?
新类型的对象应该如何被创建与销毁?
如何进行对象的初始化与赋值?
对象作为函数的参数如何以值传递?
构造函数:对类进行初始化。(作用:在对象被创建时使用特定的值构造对象,将对象初始化为特定的初始状态) 其又有:默认构造函数(有参与无参),委托构造函数(一个构造函数委托另一个构造函数完成初始化),复制构造函数(其形参为本类的对象的引用(常引用保证实参的安全性),如果未为类声明,则编译器自己生成一个隐含的复制构造函数)。
#include<iostream> using namespace std; class Clock { public: Clock(int newH,int newM,int newS); //有参构造函数 Clock(); //默认构造函数 void setTime(int newH,int newM,int newS); void showTime(); private: int hour,minute,second; }; // Clock::Clock():hour(0),minute(0),second(0){} //默认构造函数 Clock::Clock(int newH,int newM,int newS):hour(newH),minute(newM),second(newS){}//有参构造函数 Clock::Clock():Clock(0,0,0){} //委托构造函数 void Clock::setTime(int newH,int newM,int newS){ hour=newH; minute=newM; second=newS; } inline void Clock::showTime(){ cout<<hour<<":"<<minute<<":"<<second<<endl; } int main(){ Clock c1(8,10,0); Clock c2; c1.showTime(); c2.showTime(); return 0; }
复制(拷贝)构造函数的三种调用形式:
情况1,A初始化B,第一次调用复制构造函数
情况2,对象B作为fun1的实参,第二次调用复制构造
情况3,函数的返回值是类的对象,返回时,调用复制构造(赋值运算符)
#include<iostream> using namespace std; class Point{ public: Point(int a,int b); Point(); Point(const Point &p); int getX(); int getY(); private: int x,y; }; Point::Point(int a,int b):x(a),y(b){} Point::Point():x(0),y(0){} int Point::getX(){ return x; } int Point::getY(){ return y; } /*拷贝构造函数*/ Point::Point(const Point &p){ x=p.x; y=p.y; cout<<"Calling the copy constructor"<<endl; } void fun1(Point p){ cout<<p.getX()<<endl; } Point fun2(){ Point a; return a; }
组合类的初始化次序
首先对构造函数初始化列表的成员进行初始化。
成员对象构造函数的调用次序,按对象成员的定义顺序。
初始化列表中未出现的成员变量,调用默认构造函数
处理完初始化列表后,再执行构造函数的函数体。
class Line{ public: Line(Point xp1,Point xp2):p1(xp1),p2(xp2){ cout<<"Calling constructor of line!"<<endl; double x=static_cast<double>(p1.getX()-p2.getX()); double y=static_cast<double>(p1.getY()-p2.getY()); len=sqrt(x*x+y*y); } Line(Line &l):p1(l.p1),p2(l.p2){ cout<<"Calling the copy constructor of line!"<<endl; len=l.len; } ~Line(){ cout<<"Destructor Line!"<<endl; } double getLine(){ return len; } private: Point p1,p2; double len; }; int main(){ Point myp1(1,1),myp2(4,5); Line line(myp1,myp2); Line line2(line); cout<<"the length of line is "<<line.getLine()<<endl; cout<<"the length of line2 is "<<line2.getLine()<<endl; return 0; }
前向引用声明
class B;
class A{public:void f(B,b);} //A就可以使用B的成员了
class B{public:void g(A,a);}
注意:在提供一个完整的类声明之前,只能使用被声明的符号,而不能涉及类的任何细节。
class F;
class B{F f;} 错误。
class F{B b}
结构体
与类的唯一区别:类的缺省访问权限是private,而结构体是public
一般用于保存数据
如果一个结构体的全部数据成员都是公有成员,没有用户定义的构造函数,没有基类和虚函数,这个结构体可以进行如下初始化。
struct student{
int num;
string name;
char sex;
int age;
}
student xiaoming={1800,"xiaoming",'m',18};
联合体
特点:成员共用一组内存单元。
/*联合体的应用*/ #include<iostream> #include<string> using namespace std; class ExamInfo{ public: //三种构造函数 ExamInfo(string name,char grade):name(name),mode(GRADE),grade(grade){} ExamInfo(string name,bool pass):name(name),mode(PASS),pass(pass){} ExamInfo(string name,int percent):name(name),mode(PERCENTAGE),percent(percent){} void show(); private: string name; //课程名称 enum {GRADE,PASS,PERCENTAGE} mode; //计分模式 union { //无名联合体 char grade; bool pass; int percent; }; }; void ExamInfo::show(){ cout<<name<<": "; switch(mode){ case GRADE: cout<<grade;break; case PASS: cout<<(pass?"PASS":"FAIL"); break; case PERCENTAGE: cout<<percent;break; } cout<<endl; } /* 联合体 union mark{ char grade; bool pass; int percent; }; */ int main(){ ExamInfo course1("English",'B'); ExamInfo course2("C++",85); ExamInfo course3("math",true); course1.show(); course2.show(); course3.show(); return 0; }
枚举类
优势:首先强作用域:其作用域限制在枚举类型中(不同于枚举类型),其次可以指定底层类型
enum class Type{general,light,medium,heavy}; 默认底层类型为int
enum class Type:char{general,light,medium,heavy};
#include<iostream> #include<string> using namespace std; enum class side{Right,Left}; enum class thing{Right,Wrong}; int main(){ side s=side::Right; //两者不能比较 thing t=thing::Right; return 0; }
数据的共享与保护
作用域与可见性的应用:
#include<iostream> #include<string> using namespace std; int i=1; void test(){ static int a=2; static int b; //只是第一次进入被初始化 int c=10; a+=2;i+=32;c+=5; cout<<"---test---"<<endl; cout<<"i: "<<i<<" a: "<<a<<" b: "<<b<<" c: "<<c<<endl; b=a; } int main(){ static int a; int b=-10,c=0; cout<<"---main---"<<endl; cout<<"i: "<<i<<" a: "<<a<<" b: "<<b<<" c: "<<c<<endl; c+=8; test(); cout<<"---main---"<<endl; cout<<"i: "<<i<<" a: "<<a<<" b: "<<b<<" c: "<<c<<endl; i+=10; test(); return 0; }
静态成员的应用:
#include<iostream> using namespace std; class Point{ public: Point(int x=0,int y=0):x(x),y(y){count++;} Point(const Point &p){x=p.x;y=p.y;count++;} ~Point(){count--;} int getX(){return x;} int getY(){return y;} void getCount() {cout<<" Object count: "<<count<<endl;} //缺点:只能在有对象时才能调用 private: int x,y; static int count; }; int Point::count=0; int main(){ Point a(4,5); cout<<"Point A: "<<a.getX()<<","<<a.getY(); a.getCount(); Point b(a); cout<<"Point B: "<<b.getX()<<","<<b.getY(); b.getCount(); return 0; }
方法的改进:静态成员函数
#include<iostream> using namespace std; class Point{ public: Point(int x=0,int y=0):x(x),y(y){count++;} Point(const Point &p){x=p.x;y=p.y;count++;} ~Point(){count--;} int getX(){return x;} int getY(){return y;} static void getCount() {cout<<" Object count: "<<count<<endl;} private: int x,y; static int count; }; int Point::count=0; int main(){ Point::getCount(); Point a(4,5); cout<<"Point A: "<<a.getX()<<","<<a.getY(); Point::getCount(); Point b(a); cout<<"Point B: "<<b.getX()<<","<<b.getY(); b.getCount(); return 0; }
类的友元
C++提供的一种破坏数据封装和数据隐藏的机制。
作用:增加灵活性,使程序可以在封装与快速性方面做出选择。
友元的应用
friend float dist(const Point &a,const Point &b);
class A { friend class B;} B是A的友元。
#include<iostream> #include<cmath> using namespace std; class Point{ public: Point(int x=0,int y=0):x(x),y(y){} Point(const Point &p){x=p.x;y=p.y;} ~Point(){} int getX(){return x;} int getY(){return y;} friend float dist(const Point &a,const Point &b); private: int x,y; }; float dist(const Point &a,const Point &b){ double x=a.x-b.x; double y=a.y-b.y; return static_cast<float>(sqrt(x*x+y*y)); } int main(){ Point a(1,1),b(4,5); cout<<"the distance is: "; cout<<dist(a,b)<<endl; return 0; }
常类型
常对象 必须进行初始化,不能被更新(对const修饰的对象)const A a(1,1);
常函数 void print() const; 常对象将调用常函数。
常成员 用const修饰的类成员 const int a;
常引用 被引用的对象不能被更新(保证安全性) const Point &a;(原因:引用是双向传递)
常指针 指向常量的指针 int* const p;
常数组 const a[10];
指针
指针的应用
指针数组
#include<iostream> #include<cmath> using namespace std; int main(){ int a[][4]={1,2,3,4,5,6,7,8,9,10,11,12}; cout<<*a<<" "<<**a<<endl; //1的地址 1 cout<<a+1<<" "<<*(*a+4)<<endl; //5的地址 5 cout<<a+2<<" "<<**(a+2)<<endl; //9的地址 9 return 0; }
指针做函数参数
void splitfloat(float x,int*intpart,float *fracpart){ *intpart=static_cast<float>(x); *fracpart=x-*intpart; } int main(){ cout<<"整数部分为: "; float x,f; cin>>x; int n; splitfloat(x,&n,&f); /*变量地址作参数*/ cout<<"分开的整数为: "<<n<<" 分开的小数部分为: "<<f<<endl; return 0; }
- 指针类型的函数
即:函数的返回值是指针。(但不要将非静态局部地址用作函数返回值:离开函数体就失效了)
两种使用方法:
首先作为主函数定义的数组,在子函数进行某种操作后传递。#include<iostream> #include<cmath> using namespace std; int* Search(int *a,int num){ for(int i=0;i<num;i++){ if(a[i]==0) return &a[i]; } } int main(){ int arr[10]; int* Search(int* a,int num); for(int i=0;i<10;i++) cin>>arr[i]; int *zeroptr=Search(arr,10); cout<<"0的首地址为: "<<zeroptr<<" 值为: "<<*zeroptr<<endl; return 0; }
动态内存分配
#include<iostream> #include<cmath> using namespace std; int* newintvar(){ int *p=new int(); return p; //不常用(运行结束p的地址仍有效) } int main(){ int* newintvar(); int* intptr=newintvar(); *intptr=5; //访问有效 cout<<intptr<< " "<<*intptr<<endl; delete intptr; //必须释放 return 0; }
- 指向函数的指针(主要用途:实现函数回调)
函数指针定义形式:存储类型 数据类型(*函数指针名)() 函数指针指向的是程序代码存储区。
应用:将函数的指针作为参数传递给一个函数,使得在处理相同事件时可以灵活地使用不同的方法。
调用者不关心谁是被调用者,只需要知道存在一个具有特定原型和限制条件的被调用函数。
函数指针的应用:计算实现不同的功能:
#include<iostream> using namespace std; int compute(int a,int b,int(*func)(int,int)){ //传递函数的地址,不关心被调用者函数 return func(a,b); } int mmax(int a,int b){ return ((a>b)?a:b); } int mmin(int a,int b){ return ((a<b)?a:b); } int sum(int a,int b){ return a+b; } int main(){ int a=3,b=4,res; res=compute(a,b,&mmax); cout<<"max of a and b: "<<a<<" "<<b<<" is "<<res<<endl; res=compute(a,b,&mmin); cout<<"min of a and b: "<<a<<" "<<b<<" is "<<res<<endl; res=compute(a,b,&sum); cout<<"sum of a and b: "<<a<<" "<<b<<" is "<<res<<endl; return 0; }
对象指针
类名 对象指针名 Point a(1,4); Point *ptr; ptr=&a; (ptr).getX()等价于ptr->getX()
指针指向a的地址。对于动态内存 Point *ptr=new Point[2];this指针
隐含于类的每一个非静态成员函数中。指出成员函数所操作的对象。智能指针
unique_ptr 不允许多个指针共享资源。但可以用move函数转移指针。
shared_ptr 多个指针共享资源
weak_ptr 可复制shared_ptr,但其构造或者释放对资源不产生影响。
getline(cin,city); getline(cin,state,','); 分隔符的方式不同,默认行结束为分隔符深浅复制
对于默认的复制构造函数(其对于复制的内容进行了浅层复制)
深层拷贝:是对拷贝的内容重新分配了内存空间。移动构造 (C++提供的一种新的构造方法) &&右值引用 即将消亡的变量(函数返回的临时变量)
C++引入移动语义:源对象资源的控制权全部交给目标对象。
两种版本:
版本一:使用深层复制构造函数
返回时构造临时对象,动态分配将临时对象返回到主调函数,然后删除临时对象。
版本二:使用移动构造函数
将要返回的局部对象,转移到主调函数,省去了构造和删除临时对象的过程
使用深层复制构造函数应用:
#include<iostream> using namespace std; class IntNum{ public: IntNum(int x=0):xptr(new int(x)){ //构造函数 cout<<"Calling constructor..."<<endl; } IntNum(const IntNum &n):xptr(new int(*n.xptr)) { //复制构造函数 cout<<"Calling copy constructor..."<<endl; } ~IntNum(){ delete xptr; cout<<"Destructing..."<<endl; } int getInt() { return *xptr;} private: int *xptr; }; //返回值为IntNum类对象 IntNum getNum(){ IntNum a; return a; //临时对象,之后消亡 } int main(){ cout<<getNum().getInt()<<endl; return 0; }
移动构造函数应用:
#include<iostream> using namespace std; class IntNum{ public: IntNum(int x=0):xptr(new int(x)){ //构造函数 cout<<"Calling constructor..."<<endl; } IntNum(const IntNum &n):xptr(new int(*n.xptr)) { //复制构造函数 cout<<"Calling copy constructor..."<<endl; } IntNum(IntNum &&n):xptr(n.xptr){ //移动构造:偷梁换柱(效率更高,实现了转移) n.xptr=nullptr; cout<<"Calling move constructor..."<<endl; } ~IntNum(){ delete xptr; cout<<"Destructing..."<<endl; } int getInt() { return *xptr;} private: int *xptr; }; //返回值为IntNum类对象 IntNum getNum(){ IntNum a; return a; } int main(){ cout<<getNum().getInt()<<endl; return 0; }
继承
不同的继承方式影响主要体现在:
派生类成员对基类成员的访问权限/通过派生类对象对基类成员的访问权限。
对于继承的访问控制:对于基类的public和protect成员:以不同的身份在派生类中(public,private,protect),基类的private成员:不可直接访问。
- 公有继承:访问权限(派生类中的成员函数,可以直接访问基类中的public和protect成员,但不能直接访问public私有成员! 通过派生类的对象,只能访问public成员)
应用:
#include<iostream> #include<sstream> #include<string> using namespace std; class Point{ public: void initPoint(float x=0,float y=0){this->x=x;this->y=y;} void move(float offX,float offY){x+=offX;y+=offY;} float getX() const {return x;} float getY() const {return y;} private: float x,y; }; class Rectangle:public Point { public: void initRectangle(float x,float y,float w,float h){ initPoint(x,y); //调用基类公有数据成员 this->w=w; this->h=h; } float getH() const {return h;} float getW() const {return w;} private: float w,h; }; int main(){ Rectangle rect; rect.initRectangle(2,3,20,10); rect.move(3,2); cout<<"the data of rect(x,y,w,h): "<<endl; //派生类可以直接访问基类的公有成员及函数 cout<<rect.getX()<<","<<rect.getY()<<","<<rect.getH()<<","<<rect.getW()<<endl; return 0; }
- 私有继承:访问权限(派生类中的成员函数,可以直接访问基类中的public和protect成员,但不能直接访问public私有成员! 通过派生类的对象,不能直接访问public成员)
#include<iostream> #include<sstream> #include<string> using namespace std; class Point{ public: void initPoint(float x=0,float y=0){this->x=x;this->y=y;} void move(float offX,float offY){x+=offX;y+=offY;} float getX() const {return x;} float getY() const {return y;} private: float x,y; }; class Rectangle:private Point { public: void initRectangle(float x,float y,float w,float h){ initPoint(x,y); //调用基类公有数据成员 this->w=w; this->h=h; } void move(float offX,float offY){Point::move(offX,offY);} float getX() const {return Point::getX();} float getY() const {return Point::getY();} float getH() const {return h;} float getW() const {return w;} private: float w,h; }; int main(){ Rectangle rect; rect.initRectangle(2,3,20,10); rect.move(3,2); cout<<"the data of rect(x,y,w,h): "<<endl; cout<<rect.getX()<<","<<rect.getY()<<","<<rect.getH()<<","<<rect.getW()<<endl; return 0; }
保护继承
访问权限(派生类中的成员函数,可以直接访问基类中的public和protect成员,但不能直接访问public私有成员! 通过派生类的对象,不能直接访问public成员)
作用:既实现了数据的隐藏又方便继承,实现了代码的重用。类型转换:派生类的对象可以隐含转换为基类对象,可以初始化基类的引用。
派生类指针可以隐含转换为基类的指针。注意:不要重新定义继承而来的非虚函数(除非声明为虚函数)*
#include<iostream> using namespace std; class Base1{ public: //virtual void display() const {cout<<"Base1::display()"<<endl;} void display() const {cout<<"Base1::display()"<<endl;} }; class Base2:public Base1{ public: void display() const {cout<<"Base2::display()"<<endl;} }; class Derived:public Base2{ public: void display() const {cout<<"Derived::display()"<<endl;} }; void fun(Base1 *ptr){ //参数为指向基类的对象 ptr->display(); //对象指针->成员名 } int main(){ Base1 base1; //声明Base1类对象 Base2 base2; //声明Base2类对象 Derived derived; //声明Derived类对象 fun(&base1); //用Base1对象的指针调用fun函数 fun(&base2); //用Base2对象的指针调用fun函数 fun(&derived); //用Derived对象的指针调用fun函数 return 0; }
- 派生类的构造函数(每个类的构造函数是负责自己这个类的初始化的)
默认情况下:基类的构造函数不被继承,派生类需要定义自己的构造函数。
C++规定:可用using语句继承基类构造函数,但是只能初始化从基类继承的成员 语法形式:using B::B
对新增成员无能为力。
若不继承基类的构造函数
派生类新增成员:派生类定义构造函数的初始化。
继承来的成员:自动调用基类构造函数进行初始化。 - 派生类的构造函数需要给基类的构造函数传递参数。*
单继承构造函数的应用:
#include<iostream> using namespace std; class B{ public: B(); B(int i); ~B(); void print() const; private: int b; }; B::B(){ b=0; cout<<"B'S default constructor called."<<endl; } B::B(int i){ b=i; cout<<"B'S constructor called."<<endl; } B::~B(){ cout<<"B'S destructor called."<<endl; } void B::print() const { cout<<b<<endl; } class C:public B{ public: C(); C(int i,int j); ~C(); void print() const; private: int c; }; C::C(){ c=0; cout<<"B's default constructor called."<<endl; } C::C(int i,int j):B(i),c(j){ //先调用基类构造函数,再调用派生类的构造函数。 cout<<"C'S constructor called."<<endl; } C::~C(){ cout<<"C'S destructor called."<<endl; } void C::print() const { cout<<c<<endl; } int main(){ C obj(5,6); obj.print(); return 0; }
- 派生类与基类的构造函数
当基类有默认构造函数时:
派生类构造函数可以不向基类构造函数传递参数,构造派生类的对象时,基类的默认构造函数将被调用。
如需执行基类中带参数的构造函数: 派生类构造函数应为基类构造函数提供参数。
构造函数的执行顺序:
调用基类构造函数(顺序按声明的顺序,从左到右)
对初始化列表中的成员进行初始化(顺序,对象成员初始化时自动调用其所属类的构造函数。(由初始化列表提供参数))
执行派生体的构造函数体中的内容。
#include<iostream> using namespace std; class Base1{ //基类Base2,构造函数无参数 public: Base1(int i) {cout<<"Constructing Base1 "<<i<<endl;} ~Base1(){cout<<"Destructing Base1 "<<endl;} }; class Base2{ //基类Base2,构造函数无参数 public: Base2(int j) {cout<<"Constructing Base2 "<<j<<endl;} ~Base2(){cout<<"Destructing Base2 "<<endl;} }; class Base3{ //基类Base3,构造函数无参数 public: Base3() {cout<<"Constructing Base3 "<<endl;} ~Base3(){cout<<"Destructing Base3 "<<endl;} }; class Derived:public Base2,public Base1,public Base3 { public: Derived(int a,int b,int c,int d):Base1(a),member2(d),member1(c),Base2(b){} private: //派生类的私有对象 Base1 member1; Base2 member2; Base3 member3; }; int main(){ Derived obj(1,2,3,4); return 0; }
派生类的复制构造函数
若派生类没有声明复制构造函数*
编译器会在需要时生成隐含的复制构造函数。
先调用基类的复制构造函数
再为派生类新增的成员执行复制。若派生类定义复制构造函数*
一般都要为基类的复制构造函数提供参数,复制构造函数只能接收一个参数,既用来初始化派生类定义的成员,也将被传递给基类的复制构造函数。
基类的复制构造函数形参类型是基类对象的引用,实参可以是派生类对象的引用。派生类的析构函数
析构函数不被继承,派生类如果需要要自行声明析构函数
声明方法与无继承关系时类的析构函数相同。
不需要显示的调用基类的析构函数,系统会自动隐式调用。
先执行派生类析构函数的函数体,再调用基类的析构函数。访问从基类继承的成员
当派生类与基类中有相同的成员时
若未特别限定,则通过派生类对象使用的是派生类中的同名成员。
如要通过派生类对象访问基类中被隐藏的同名函数,应使用基类名和作用域操作符::来限定。
#include<iostream> using namespace std; class Base1{ //基类Base1,构造函数无参数 public: int var; void fun(){cout<<"Member of Base1"<<endl;} }; class Base2{ //基类Base2,构造函数无参数 public: int var; void fun(){cout<<"Member of Base2"<<endl;} }; class Derived:public Base1,public Base2{ //基类Base3,构造函数无参数 public: int var; void fun(){cout<<"Member of Derived"<<endl;} }; int main(){ Derived d; Derived *p=&d; d.var=1; d.fun(); d.Base1::var=2; //作用域分辨标识 d.Base1::fun(); d.Base2::var=3; //作用域分辨标识 d.Base2::fun(); return 0; }
- 处理二义性-->虚基类
声明:class B1:virtual public B;
作用:主要用来解决多继承时可能发生的同一基类继承多次而产生的二义性问题
为最远的派生类提供唯一的基类成员,而不重复产生多次复制
注:在第一级继承时就要将共同基类设计为虚基类。 - 虚基类及其派生类构造函数
建立对象时,所指定的类称为最远派生类
一般只有最远派生类的构造函数调用虚基类的构造函数,其他类被忽略
#include<iostream> using namespace std; class Base0{ //基类Base1,构造函数无参数 public: int var0; void fun0(){cout<<"Member of Base0"<<endl;} }; class Base1:virtual public Base0{ //继承虚基类 public: int var1; }; class Base2:virtual public Base0{ //基类Base3,构造函数无参数 public: int var2; }; class Derived:public Base1,public Base2{ public: int var; void fun(){cout<<"Member of Derived"<<endl;} }; int main(){ Derived d; d.var0=2; //直接访问虚基类的数据成员 d.fun0(); //直接访问虚基类的函数成员 return 0; }
- 虚基类及其派生类构造函数
#include<iostream> using namespace std; class Base0{ //基类Base1,构造函数无参数 public: Base0(int var):var0(var){} int var0; void fun0(){cout<<"Member of Base0"<<endl;} }; class Base1:virtual public Base0{ //继承虚基类 public: Base1(int var):Base0(var){} int var1; }; class Base2:virtual public Base0{ //基类Base3,构造函数无参数 public: Base2(int var):Base0(var){} int var2; }; class Derived:public Base1,public Base2{ public: Derived(int var):Base0(var),Base1(var),Base2(var){} //需给最远基类传递参数 int var; void fun(){cout<<"Member of Derived"<<endl;} }; int main(){ Derived d(1); d.var0=2; //直接访问虚基类的数据成员 d.fun0(); //直接访问虚基类的函数成员 return 0; }
虚函数
其用virtual关键字说明,实现运行时多态。
虚函数必须是非静态成员函数。一般函数可以是虚函数(其是属于对象的,不是属于类的,它需要在运行时用指针去定位到它指向的对象是谁,然后再决定去调用那个结构体或类)虚函数声明只能出现在类定义中的函数原型声明中,而不能在成员函数实现的时候*
构造函数不能为虚函数,而析构函数可以为虚函数。*
虚函数(动态绑定)一般不声明为内联函数(其静态绑定)*
虚析构函数
如果你打算允许其他人通过基类指针调用对象的析构函数,就需要让基类的析构函数成为虚函数,否则执行delete的结果不确定。
应用:
#include<iostream> using namespace std; class Base{ public: virtual ~Base(); //不是虚函数 }; Base::~Base(){ cout<<"Base destructor"<<endl; } class Derived:public Base{ public: Derived(); virtual ~Derived(); //不是虚函数 private: int *p; }; Derived::Derived(){ p=new int(0); } Derived::~Derived(){ cout<<"Derived destructor"<<endl; delete p; } void fun(Base* b){ //声明为虚析构函数会使其进行动态绑定 delete b; //静态绑定 } int main(){ Base *b=new Derived(); //不声明虚析构函数,会导致内存泄漏! fun(b); //声明之后,会先调用Derived的析构函数,再调用Base的析构 return 0; }
- 虚表与动态绑定应用
#include<iostream> using namespace std; class Base{ public: //虚表中有:f() g() virtual void f() {cout<<"Base::f()"<<endl;} virtual void g() {cout<<"Base::g()"<<endl;} private: int i; }; class Derived:public Base{ public: //虚表中有:f() g() h() virtual void f(){cout<<"Derived::f()"<<endl;} //覆盖了Base::f() virtual void h(){cout<<"Derived::h()"<<endl;} private: int j; }; int main(){ Base a; Derived c; a.f();a.g(); //其无h()函数 c.f();c.g();c.h(); Base *b=new Derived(); //虚函数机制 (*b).f();(*b).g(); //其无h()函数 return 0; } /* 返回值为: Base::f() Base::g() Derived::f() Base::g() Derived::h() Derived::f() Base::g() */
- 纯虚函数
其是抽象类,在基类中声明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据需要定义自己的版本,其声明格式为 virtual 函数类型 函数名(参数表)=0;
作用:将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。
对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现。 - 抽象类只能作为基类来使用,不能定义抽象类对象 **
#include<iostream> using namespace std; class Base1{ public: virtual void display() const = 0; //纯虚函数 }; class Base2:public Base1{ public: virtual void display() const; //覆盖基类的虚函数 }; void Base2::display() const{ cout<<"Base2::display()"<<endl; } class Derived:public Base2{ public: virtual void display() const; //覆盖基类的虚函数 }; void Derived::display() const{ cout<<"Derived::display()"<<endl; } void fun(Base1 *ptr){ ptr->display(); } int main(){ Base2 base2; Derived derived; fun(&base2); fun(&derived); return 0; } /* Base2::display() Derived::display() */
- override
作用:基类声明虚函数,派生类声明一个函数覆盖该虚函数。
覆盖要求:函数签名完全一致,签名包括:函数名,参数列表,const
问题:
#include<iostream> using namespace std; class Base{ public: virtual void fun(int) const; virtual ~Base(){}; }; void Base::fun(int) const{ cout<<"Base fun"<<endl; } class Derived:public Base{ public: void fun(int); //未加const,未能覆盖基类的fun函数(jiabujia) ~Derived(){}; }; void Derived::fun(int){ cout<<"Derived fun"<<endl; } int main(){ Base *b; b=new Base; b->fun(1); b=new Derived; b->fun(1); return 0; } /* Base fun Base fun */
- final 不允许被继承。
struct Base1 final{};
struct Derived:Base1{}; //编译错误,Base1为final,不允许被继承
struct Base2{
virtual void fun() final;
};
struct Derived2:Base2{
void fun(); //编译错误,Base2::fun为final,不允许被覆盖
};
IO流与异常处理
cin在读取char值时,与读入其他类型一样,将忽略空格与换行符。
cin.get(ch); 读取输入中的下一个字符,可读取空格,换行符等。
getline(cin,str) getline(cin,str,',')
- 输出流相关的函数
open函数 把该流与一个特定磁盘文件相关联。
read成员函数 从一个文件读字节到一个指定的内存区域,由长度参数确定要读的字节数,当遇到文件结束或者在文本模式文件中遇到文件结束标记字符时结束。
seekg函数设置文件输入流中读取数据位置的指针
tellg函数返回当前文件读指针的位置
close函数关闭一个文件输入流关联的磁盘文件。
应用:字符串转化为数值
#include<iostream> #include<sstream> using namespace std; template<class T> inline T fromString(const string &str){ istringstream is(str); //创建字符串输入流 T v; is>>v; //从字符串输入流中读取数据v return v; //返回变量v; } int main(){ int v1=fromString<int>("5"); cout<<v1<<endl; double v2=fromString<double>("5.2"); cout<<v2<<endl; return 0; }
- 文件输出流成员函数
open函数 把流与一个特定的磁盘文件关联起来,指定打开模式
put 把一个字符写到输出流
write 把内存中的一块内容写到文件输出流中。
#include<iostream> #include<sstream> #include<string> using namespace std; template<class T> inline string toString(const T &v){ ostringstream os; //创建字符串输出流 os<<v; //将数据v写入字符串流 return os.str(); //返回输出流生成的字符串 } int main(){ string str1=toString(5); cout<<str1<<endl; string str2=toString(5.2); cout<<str2<<endl; return 0; }
插入运算符与操纵符一起工作:控制输出格式。
很多操纵符都存在ios_base类中,如(hex,oct,dec) setw与width函数设置输出宽度。
操纵符的应用(iomanip)
#include<iostream> #include<iomanip> #include<string> using namespace std; int main(){ double v[]={1.23,3.45,5.67,9.685,123.12}; string s[]={"xiaoming","xiaoliang","xiaoli","xiaohua","xiaomei"}; cout<<"结果为: "<<endl; cout<<setiosflags(ios_base::fixed); //设置输出类型 cout<<setiosflags(ios_base::scientific); for(int i=0;i<5;i++){ //setw()默认右对齐 //cout.width(10); cout<<setw(10)<<s[i]<< setw(8)<<v[i]<<endl; } for(int i=0;i<5;i++){ //cout.width(10); cout<<setiosflags(ios_base::left)<<setw(10)<<s[i]<< //左对齐 resetiosflags(ios_base::left)<<setw(8)<<v[i]<<endl; //取消左对齐 } return 0; }
- 异常的捕获:
#include<iostream> using namespace std; int divide(int x,int y){ if(y==0) throw x; return x/y; } int main(){ try{ cout<<"5/2 = "<<divide(5,2)<<endl; cout<<"8/0 = "<<divide(8,0)<<endl; cout<<"7/1 = "<<divide(7,1)<<endl; } catch(int e){ //捕获到异常后不执行之后的运算。 cout<<e<<" is divided by zero!"<<endl; } cout<<"that is ok.!"<<endl; return 0; }
- 异常接口声明:
一个函数显式的声明可能抛出的异常,有利于函数的调用者为异常处理做好准备。
定义形式如:void func() throw(A,B,C,D); 若无异常接口声明:void throw()可抛掷任何类型的异常。 - 异常的构建与析构
其内部实现自动的析构(发现异常后,其后面的内容不再执行)
即找到一个匹配的catch异常处理后 初始化异常参数 将从对应的try块开始到异常被抛出处之间构造的所有自动对象进行析构 从最后一个catch处理之后开始恢复执行 - 标准异常的基础
exception 标准程序库异常类的公共基类
logical_error 可以在程序中预先检测到的异常
runtime_error 难以被预先检测的异常
应用如下:求三角形面积
#include<iostream> #include<cmath> #include<stdexcept> using namespace std; double area(double a,double b,double c) throw(invalid_argument) { //判断边长是否为正 if(a<=0||b<=0||c<=0) throw invalid_argument("the side length must be positive!"); if(a+b<=c||b+c<=a||a+c<=b) throw invalid_argument("the side length should fit the triange inequation!"); double s=(a+b+c)/2; return sqrt(s*(s-a)*(s-b)*(s-c)); } int main(){ double a,b,c; cout<<"please input the side length: "; cin>>a>>b>>c; try{ double s=area(a,b,c); cout<<"area is: "<<s<<endl; } catch(exception &e){ //共有基类 cout<<"error is: "<<e.what()<<endl; } return 0; }
运算符重载
C++几乎可以重载全部地运算符(不能重载的运算符有". .* :: ?:)
运算符是针对新类型数据的实际需要,对原有运算符进行适当的改造。
如:使复数类的对象可以用“+”,使时钟类对象可以用“++”,实现时间增加1s。
将运算符重载为类成员的运算符函数定义形式为:
函数类型 operator 运算符(形参){}
参数个数=源操作个数-1 (后者++,--除外)双目运算符重载规则
如果要重载B的类成员函数。表达式为 oprd1 B oprd2
经重载后,表达式相当于oprd1.operator B(oprd2)
例题1:双目运算符复数类加减法运算重载为成员函数 要求:将+,-运算重载为复数类的成员函数;规则:实部与实部相加,虚部虚部相加。操作数:都是复数类型。
#include<iostream> using namespace std; class Complex{ public: Complex(double r=0.0,double i=0.0):real(r),imag(i){} Complex operator+(const Complex &c2) const; //+重载成员函数 Complex operator-(const Complex &c2) const; //-重载成员函数 void display() const; //输出参数 private: double real; //复数实部 double imag; }; Complex Complex::operator+(const Complex &c2) const{ //创建一个临时无名对象作为返回值 return Complex(real+c2.real,imag+c2.imag); } Complex Complex::operator-(const Complex &c2) const{ //创建一个临时无名对象作为返回值 return Complex(real-c2.real,imag-c2.imag); } void Complex::display() const{ cout<<"("<<real<<","<<imag<<")"<<endl; } int main(){ Complex c1(5,4),c2(2,10),c3,c4; cout<<"c1= ";c1.display(); cout<<"c1= ";c2.display(); c3=c1-c2; //使用重载运算符 cout<<"c3=c1-c2 ";c3.display(); c4=c1+c2; //使用重载运算符 cout<<"c4=c1+c2 ";c4.display(); return 0; }
- 单目运算符(前置单目运算符,后置单目运算符)
- 前置单目运算符重载规则:
如果要重载U为类成员函数,使之能够实现表达式U oprd,其中oprd为A类对象,则U应被重载为A类的成员函数,无形参。 经重载后 其相当于oprd.operator(U) - 后置单目运算符规则:(++与--重载规则)
如果要重载++或--为类成员函数,使之实现oper++或oper--,其中oper为A类对象,则++或--应被重载为A类的类成员函数,且具有一个int类型形参。 经重载后 oper++其相当于oprd.operator(0)
例题2:重载时钟类的前置++与后置++为时钟类成员函数。(实现时钟自加1)
#include<iostream> using namespace std; class Clock{ public: Clock(int hour=0,int minute=0,int second=0); void showTime() const; Clock& operator++(); //前置运算符 Clock operator++(int); //后置运算符 private: int hour,minute,second; }; Clock::Clock(int hour,int minute,int second){ if(0<=hour&&hour<24&&0<=minute&&minute<60&&0<=second&&second<60){ this->hour=hour; this->minute=minute; this->second=second; } else cout<<"Time error!"<<endl; } void Clock::showTime() const { cout<<hour<<":"<<minute<<":"<<second<<endl; } Clock &Clock::operator++(){ //前置要返回引用 second++; if(second>=60){ second-=60;minute++; if(minute>=60){ minute-=60;hour=(hour+1)%24; } } return *this; } Clock Clock::operator ++(int){ //注意形参表中的整型参数 Clock old=*this; ++(*this); //调用前置++运算符 return old; } int main(){ Clock myClock(23,59,59); cout<<"First time output: "; myClock.showTime(); cout<<"show myclock++: "; (myClock++).showTime(); cout<<"show ++myclock: "; (++myClock).showTime(); return 0; }
运算符重载为类外的非成员函数
规则:函数的形参代表依自左向右次序排列的个操作数。
重载为非成员函数时,参数个数=原参数个数(后置++,--除外)
至少应该有一个自定义类型的参数。
后置单目运算符++与--的重载函数,形参列表中要增加一个int,但不必写形参名。
如果在运算符的重载函数中需要操作某类对象的私有成员,可以将次函数声明为该类的友元。运算符重载为类外的非成员函数规则:
双目运算符,B重载之后, oprd1 B oprd2 等价于 operator B(oprd1,oprd2)
前置单目符 B oprd 等同于 B(oprd)
后置单目运算符 oprd B 等价于 operator B(oprd,0)
例题3:重载Complex的加减法和"<<"运算符为非成员函数。
将+,-双目运算符重载为非成员函数,并将其声明为复数类的友元,两个操作数都是复数类的常引用。
将<<双目运算符重载为非成员函数,它的左操作数是std::ostream引用,右操作数为复数类的常引用,返回std::ostream引用,用以支持下面形式的输出
cout<<a<<b;级联输出
调用 operator<<(operator<<(cout,a),b);
#include<iostream> using namespace std; class Complex{ public: Complex(double r=0.0,double i=0.0):real(r),imag(i){} friend Complex operator+(const Complex &c1,const Complex &c2); //+重载非成员函数 friend Complex operator-(const Complex &c1,const Complex &c2); //-重载非成员函数 friend ostream & operator<<(ostream &out,const Complex &c); private: double real; //复数实部 double imag; }; //非成员函数 Complex operator+(const Complex &c1,const Complex &c2){ //创建一个临时无名对象作为返回值 return Complex(c1.real+c2.real,c1.imag+c2.imag); } Complex operator-(const Complex &c1,const Complex &c2){ //创建一个临时无名对象作为返回值 return Complex(c1.real-c2.real,c1.imag-c2.imag); } ostream & operator<<(ostream &out,const Complex &c){ out<<"("<<c.real<<","<<c.imag<<")"; return out; } int main(){ Complex c1(5,4),c2(2,10),c3,c4; cout<<"c1= "<<c1<<endl; cout<<"c1= "<<c2<<endl;; c3=c1-c2; //使用重载运算符 cout<<"c3=c1-c2 "<<c3<<endl; c4=c1+c2; //使用重载运算符 cout<<"c4=c1+c2 "<<c4<<endl; return 0; }