c++面向对象程序设计 通俗版(未完

概述

面向对象程序设计的特点:抽象、封装、继承、多态

类的定义

用class定义类,类中包括public公有,protected受保护,private私有类型的成员(继承才会用到protected类型,平常它和private一样,所以平常也不用)。
成员分数据和函数。
public是对外的接口,里面放函数;private是类中的私有成员,一般放数据。当然public里面也可以放数据,private也可以放函数,但是一般正常人不这么干。
不写类型默认是private。
代码规范:先写public,后写private,数据按类型从小到大说明
类的说明部分可以放到头文件,实现部分放到cpp文件里(开发的时候注意,写作业就算了)

class Clock{
	public:
		void set(int newh,int newm,int news);
		void showtime();
	private:
		int h,m,s;
};
void Clock::set(int newh,int newm,int news){
	h=newh;m=newm;s=news;
}
void Clock::showtime(){
	cout<<h<<" "<<m<<" "<<s<<endl;
}

函数可以先声明,在外面定义,此时需要加一个 类:: 表示它是这个类的,也可以声明的时候直接定义(好像不符合代码规范,但是我写作业经常这么干)。
函数可以直接访问类中的数据成员。
注意:

  • 数据的初始化是在声明对象的时候进行的,不能在类里直接初始化
  • 数据可以是c++自带数据类型,也可以是自定义的结构体,也可以是指针和引用

声明对象

有了类就可以声明类类型(不知道谁翻译的类这个词)的对象了
单个对象、对象数组、指针、引用都可以声明

	Clock a;
	Clock b[10];
	Clock *c=&a;
	Clock &d=a;

对象成员

用.或->可以使用对象的公有成员,函数需要传正确的参。普通的对象、数组和引用 用. ,指针用->,

	a.showtime(); 
	c->set(13,21,45);
	d.showtime();

对象的初始化

构造函数和析构函数

构造函数:创建对象时自动调用,没有函数类型,名字和类一致
析构函数:程序结束时自动调用,没有函数类型,写个~,然后名字和类保持一致,因为没有参数所以不能重载

public:
	Clock(int a,int b,int c);
	~Clock();

下面是带参数的构造函数

public:
	Clock(int a,int b,int c){
			h=a;m=b;s=c;
		}

这两个函数系统都提供默认的,so不写也可以,但是默认构造函数=没有参数,也可以手写默认构造函数(也就是不带参数的构造函数),把赋值写死在函数里。

public:
	Clock(){
		h=0;m=0;s=0;
	}

如果要往构造函数里面加东西,比如输出一行“调用了构造/析构函数”,就可以手写一个。

public:
	Clock(int a,int b,int c){
		h=a;m=b;s=c;
		cout<<"调用构造函数"<<endl;
	}
	~Clock(){
		cout<<"调用析构函数"<<endl;
	}

声明对象的时候括号里直接赋值,这就是调用了构造函数来初始化
Clock e(0,0,0);

拷贝构造函数

用一个对象构造一个新对象,参数是引用

public:
	Clock(clock &A){
		h=A.h;m=A.m;s=A.s;
	}

调用: Clock e(d);

内联函数

函数体被语句直接替换,语句中没有复杂结构,提高运行效率,很像宏定义
使用inline声明

public:
inline int fun(){
			cout<<1<<endl;
		}

函数重载

函数名相同,但参数的个数或者类型不同。只要可区分传参,就是重载。
带参数、不带参数的构造函数以及拷贝构造函数就是一种重载

public:
		Clock(int a,int b,int c){
			h=a;m=b;s=c;
			//cout<<"调用构造函数"<<endl;
		}
		Clock(){
			h=0;m=0;s=0;
		}
		Clock(Clock &A){
			h=A.h;m=A.m;s=A.s;
		}

参数默认值

参数排列方式是先没有默认值的函数,后有默认值的函数,否则会影响重载(如果传入不足量的参数,到底给谁会产生歧义)

public:
		Clock(int a=0,int b=0,int c=0){
			h=a;m=b;s=c;
		}
	Clock a(1,2);
	a.showtime(); 

输出结果为1 2 0

public:
		Clock(int a,int b=0,int c=0){
			h=a;m=b;s=c;
		}
	Clock a(1);
	a.showtime(); 

输出为1 0 0,传入的参数优先给函数声明时最左边的变量

静态成员

在类内使用static说明静态成员,可以是数据,也可以是函数。

private:
		static int cnt;
public:
	static int c(){
			return cnt;
		}

数据要在全局区域初始化,且必须初始化
不需要在前面加static,但nj上需要

int Clock::cnt=0;

在静态成员函数中可以直接引用静态成员,但引用非静态成员必须加对象名(也就是必须传一个对象进去)。这很好理解,因为静态成员是该类所有对象的,调用的时候不分你我,类似于全局函数

public:
		static void b(Clock A){
			cout<<A.h<<endl;//如果直接引用h会报错
		}

引用静态成员函数有两种方式:对象.函数()或类::函数()。你会觉得有些滑稽,为什么用a.去调用静态成员函数,还要传参自己进去,实际上这里a.表明了该类在调用该函数,它并不像通常的成员函数

	a.b(a);
	Clock::b(a);

友元

友元函数在类中说明,加关键字friend。它并不是成员函数,但可以访问该类所有成员。在类体外说明时,不加关键字,不加类名。
调用时看作普通函数,不是成员函数

public:
	friend int sum(Clock A){
			cout<<A.h+A.m+A.s<<endl;
		}

调用函数直接sum(A);
而不是A.sum(); 会报错[Error] 'class Clock' has no member named 'sum'

友元类可访问对方的成员

  • 友元关系不可逆
  • 友元关系不可传递

可以在友元类中访问对方的所有成员,包括私有成员

class Clock{
	friend class Time;
	public:
		Clock(int a,int b=0,int c=0){
			h=a;m=b;s=c;
		}
	private:
	int h,m,s;
};
class Time{
	private:
		int day;
	public:
		void printfriend(Clock a){
			cout<<a.h<<endl;
		}
};
signed main(){
	Clock d(1,2,3);
	Time x;
	x.printfriend(d);
	return 0;
}

输出为1

指针和引用

指向成员的指针

指向数据成员的指针

指向成员的指针只能访问公有成员
为了演示这种用法,我写了一个A类,里面有公有数据成员c和公有函数成员fun

class A{
	public:
		int c;
		A(int b,int d){
			x=b;c=d;
		}
		int fun(int t){
			cout<<"welcome to func function"<<endl;
			return t+1;
		}
	private:
		int x;
};

指向数据成员的指针和普通的指针的写法很像,只是指针前以及变量前都加了类名::限定

	A a(8,4);
	int A::*pc=&A::c;
	cout<<a.*pc<<endl;

由此可以考虑为什么指针只能指向公有成员,因为私有成员不可在主函数中访问,而如果在类的函数里访问成员,似乎指针派不上用场。
当这个对象也是一个指针的时候,书写就变得抽象起来。
需要注意的是,指向数据的指针的初始化需要加引用符号。

	A *p=&a;
	cout<<p->*pc<<endl;

该类的任意一个对象都可以用这个指针指代自己的某个公有数据成员,它是对整个类生效的,类似于给公有数据成员换个名字

指向函数成员的指针

这种用法更加无人问津而且抽象
它的声明方式
函数返回值类型 (类名::*指针名)(参数表)=类名::函数名

int (A::*pfun)(int)=A::fun; 

调用方式(a.*pfun)(9);,必加括号!!
观察发现,参数表居然只需要写明参数类型,验证了一下发现它和int (A::*pfun)(int t)=A::fun; 是等效的
那么没有参数表&返回值是void的函数可以写吗?也没问题

void ppp(){
	cout<<"111"<<endl;
}
void (A::*pfun2)()=A::ppp;//声明函数指针
(a.*pfun2)();//调用。输出是111

当对象自己也是指针的时候,这样写

	A *p=&a;
	(p->*pfun)(9);

对象指针和引用作函数参数

对象指针作为函数参数

仅将对象的地址值传给形参

class B{
	public:
		B(int X){x=X;}
		void print(B *p){
			cout<<(p->x)+x<<endl;
		}
	private:
		int x;
};
signed main(){
	B a(3);
	B b(5);

	B *p=&a;
	b.print(p);
	return 0;
}
对象引用作为函数参数
class B{
	public:
		B(int X){x=X;}
		void print(B &p){
			cout<<(p.x)+x<<endl;
		}
	private:
		int x;
};
signed main(){
	B a(3);
	B b(5);
	b.print(a);
	return 0;
}

this指针

平常调用成员函数的时候,访问成员不需要加任何东西,实际上隐含使用了this指针,系统先将对象的地址赋给this指针,然后用 this->成员名 访问成员

class B{
	public:
		B(int X){x=X;}
		B(){x=0;}
		void print(){
			cout<<x<<endl;
		}
		void Copy(B &b){
			if(this==&b)return;
			*this=b;
		}
	private:
		int x;
};
signed main(){
	B b(3),c;
	c.Copy(b);
	c.print();
	return 0;
}

对象数组

和普通的数据类型的数组一样
声明: 类名 数组名[个数]
访问: 数组名[下标].成员名
初始化: B b[3]={B(1),B(2),B(3)};
声明这个数组,里面的每个对象都会调用构造函数
下面这个代码,按顺序调用了:
构造函数x6、析构函数、构造函数、析构函数x6

void main()
     {
         Student stu[5]={ Student("Ma",5019001,94),
			  			Student("Hu",5019002,95),
			  			Student("Li",5019003,88)};
         stu[3] = Student("Zhu",5019004,85);
         stu[4] = Student("Lu",5019005,90);
         stu[1].Setscore(98);
         for(int i(0);i<5;i++)
             stu[i].Print();
     }

指向数组的指针

有点麻,晚点写

指针数组

const

常对象

  • 常对象的数据成员都是常数据成员,不能修改值
  • 常对象只能调用常成员函数
	const B b(3);
	B const d(4);

常指针&常引用

	B b(3);
	B *const p1=&b;//地址值为常量的指针,不能改指针的指向,但可以改值
	B const *p2=&b;//所指向的值为常量的指针,不能改它指向的值,但可以改指针的指向
	const B &y=b;//常引用,b可以改,y不能改

常数据成员

private:
		const int y;

不可修改,它的初始化通过构造函数的成员初始化实现,写法特殊B(int X,int Y):y(Y){x=X;}

静态常数据成员static const int z;
全局初始化const int B::z=15;

常成员函数

常成员函数可以引用各种数据成员,但不能修改任何成员。
一个只可读的函数有什么用? 常对象只能调用常成员函数,因此必须有常成员函数给它用。

void ccc(int a)const{
		cout<<x+a<<endl;
	}

调用B b(3);b.ccc(6);
输出为9
以下代码会报错[Error] assignment of member 'B::x' in read-only object

		void ccc(int a)const{
			x=a;
		}

子对象

前文已说,类的成员可以各种各样的,那么另一个类的对象也可以当它的数据成员,这就是子对象

class A{
	public:
		int c;
		A(int b,int d){
			x=b;c=d;
		}
		void printx(){
			cout<<x<<endl;
		}
	private:
		int x;
};
class C{
	public:
		C(int i,int j,int k):a(i,j){//子对象的初始化在构造函数中完成,如果有很多子对象,用逗号隔开
			b=k;
		}
		void print(){
			a.printx();
			cout<<b<<endl;
		}
	private:
		A a;//子对象
		int b;
};
	C c(1,2,3);
	c.print();

输出1 3

堆对象(动态对象)

用new创建动态对象,用法和new普通数据类型一样

	C *pc=new C(1,2,3);
	pc->print();

new对象数组,比起malloc的好处就是不用写10*sizeof(class C)

C *pcarray=new C[10];

这里没有给每个C的对象赋初始值,记得手写一个默认构造函数

C():a(0,0){b=0;}

之后可以通过重载赋值运算符给每个对象赋值,右侧需要先转换成一个对象再赋值,这个用法详见后续的重载运算符

delete释放动态对象或对象数组

	delete pc;
	delete []pcarray;

类型的隐含转换

这用户自定义的类型转换(重载)还没写

  • 低类型自动转换为高类型
  • 赋值运算符右侧自动转换为左侧变量的类型
  • 实参类型自动转换为形参类型之后传值
  • 自动将返回的表达式转换为函数返回值类型之后返回给调用函数
  • 构造函数具有类型转换功能
	C c;
	c={20,10,5};
class D{
	public:
		D(){d=0;}
		D(double i){d=i;}
		print(){cout<<d<<endl;
		}
	private:
		double d;
};
signed main(){
	D d;
	d=20;
	d.print();
	return 0;
}
  • 类型转换函数 operator
    D类有两个int数据成员,当它和double类型的a相加时,自动转换为double类型,而该类的double转换函数定义为den/num
class D{
	public:
		D(int i,int j){den=i;num=j;}
		operator double(){
			return double(den)/double (num);
		}
	private:
		double den,num;
};
signed main(){
	D d(6,10);
	double a(3.5);
	a+=d;
	cout<<a<<endl;
	return 0;
}

运算符重载

自定义类的运算
写法是:operator 运算符(含一元、二元)
当运算符函数是成员函数时,第一个操作数就是调用运算符函数的这个对象(也就是this指向的对象),因此第一个数不用传。so一元运算符不需要传参,二元运算符只需要提供右操作数。
当运算符函数是全局函数->友元函数时,操作数都要传,其中至少一个操作数是类类型的。

  • 这些运算符不能重载::..*?:
  • 不能定义c++没有的运算符
  • 只能定义自己声明的类的运算符
    -不能改变操作数的个数
    在这里插入图片描述

成员函数形式的一元运算符函数重载

class Byte{
	char b;
	public:
		Byte(char B=0):b(B){}
		void print(){cout<<b<<endl;}
		//常成员函数,只可读不可写
		const Byte& operator+()const{return *this;}//返回Byte指针
		const Byte operator-()const{return Byte(-b);}//返回Byte
		Byte operator!()const{return Byte(!b);}
};
signed main(){
	Byte B('b');
	Byte C(-B);//调用-运算符函数
	B.print();
	C.print();
	return 0;
}

下面的运算符会改变对象,因此不定义为常成员函数

public:
	const Byte& operator++(){//前缀++,返回改变后的对象 
		b++;return *this;
	}
	//int函数是用来区分前后缀的,调用时系统自动传入0,在函数中并不使用
	const Byte operator++(int){//后缀++,返回之前的对象 
		Byte before(b);
		b++;
		return before;
	}
signed main(){
	Byte B('b');
	Byte C('a');
	C=B++;
	B.print();
	C.print();
	C=++B;
	B.print();
	C.print();
	return 0;
}

输出是 c b d d

  • 重载自增和自减运算符时,应同时定义前缀式和后缀式
  • 重载运算符的含义要和正常人保持一致

全局友元函数形式的一元运算符函数重载

class Integer{
	long i;
	Integer* This(){return this;}
	public:
		Integer(long ll=0):i(ll){}
		friend const Integer& operator+(const Integer& a);//正号 
		friend const Integer operator-(const Integer& a);//负号
		friend Integer* operator&(Integer& a);
		friend const Integer& operator ++(Integer& a);//前缀++ 
		friend const Integer operator++(Integer& a,int); //后缀++ 
};
const Integer& operator+(const Integer& a){
	return a;
} 
const Integer operator-(const Integer& a){
	return Integer(-a.i);
}
Integer* operator&(Integer& a) {
  		return a.This( ); // 不能用&a,会导致递归调用本函数
}
const Integer& operator ++(Integer& a){
	a.i++;return a;
}
const Integer operator++(Integer& a,int){
	Integer before(a);a.i++;return before;
}

成员函数形式的二元运算符函数重载

public:
	const Byte operator+(const Byte& right)const{
	return Byte(b+right.b);}
	const Byte operator/(const Byte& right)const{
	assert(right.b!=0);return Byte(b/right.b);}
	Byte& operator=(const Byte& right) { // 只能用成员函数重载
		if(this == &right) return *this; // 自赋值检测
	  		b = right.b;
	  		return *this; 
		}

赋值号= 只能用成员函数重载

全局友元函数形式的二元运算符函数重载

public:
	friend const Integer operator+(const Integer& left, const Integer& right);
	friend const Integer operator&(const Integer& left, const Integer& right);
	friend Integer& operator+=(Integer& left, const Integer& right);
	friend bool operator==(const Integer& left, const Integer& right);
const Integer operator+(const Integer& left,  const Integer& right) {
  		return Integer(left.i + right.i);
}
const Integer operator&(const Integer& left,  const Integer& right) {
  		return Integer(left.i & right.i);
}
Integer& operator+=(Integer& left, const Integer& right) {
   		if(&left == &right) {/* self-assignment */}
   		left.i += right.i;
   		return left;
}
bool operator==(const Integer& left,  const Integer& right) {
    return left.i == right.i;
}
  • 参数如果只读值,传const,如果同时还是成员函数重载,那就定义成const成员函数
  • 返回值类型看情况。值、对象、指针、引用(原有对象)
  • 赋值运算符均改变左值,为了使赋值结果能应用于链式表达式a=b=c,要返回一个改变了的左值的引用
  • 返回值优化。Byte(b+right.b)看上去是调用了构造函数,其实编译器优化成了临时对象语法,效率更高
  • 使用成员运算符的限制是左操作数必须是该类的对象,不能自动类型转换,这种时候可以用全局运算符,类似的情况也会出现在i/o流重载
class Number {
	int i;
	public:
	  	Number(int ii = 0) : i(ii) {}
	  	const Number operator+(const Number& n) const {return Number(i + n.i);} 
		friend const Number operator-(const Number&, const Number&);
};
const Number operator-(const Number& n1, const Number& n2){ //友元
	return Number(n1.i - n2.i);
}
signed main(){
	Number a(4),b(11);
	a+b;//可以
	a+1;//可以,右操作数转换成Number
	//1+a;//不可以,左操作数不是Number
	a-b;//可以
	a-1;//可以,右操作数转换成Number
	1-a;//可以,左操作数转换成Number
	return 0;
}

重载输入输出流>> <<

全局函数重载输入输出运算符

class complex{
	private:
  		double real, image;
	public:
		complex(double r = 0, double i = 0){real = r; image = i; } 
		const complex operator+(const complex& right) const{
	    	return complex (real+right.real,image+right.image); }
		friend ostream& operator<<(ostream& os, const complex& c);
		friend istream&  operator>>(istream& is,complex& c);
};
ostream& operator<<(ostream& os, const complex& c){
	if(c.real==0 && c.image==0){ os << "0"; }
	if(c.real!=0){ os << c.real; }
	if(c.image!=0){
		if(c.image>0 && c.real!=0)os << "+";
		os << c.image << "i" ;
 	}
	return os; //返回ostream对象便于链式表达式
}
istream& operator>>(istream& is, complex& c){
	cout<<"please input a complex:";
	return is>>c.real>>c.image;
}

不过注意这个类名和标准C++复数运算库撞了
输出
在这里插入图片描述

重载赋值运算符=

  • 编译器有默认的赋值运算符
  • 初始化的时候不会调用,只有左侧是已存在的对象的时候才会调用operator=
  • 不能用全局函数重载,否则会导致重定义内置的赋值运算符
  • 赋值检测 来看一个例子
class my_string{
	char* str;
	int len;
	public:
	my_string(const char* s = ""){
    			len = strlen(s);
    			str = new char[len + 1];
    			strcpy(str, s);
	}
	~my_string(){delete[]str;}
	my_string& operator=(const my_string& s);
};

想要实现b给a赋值

	my_string a("abcde"),  b("hijk");
	a = b;

其中成员函数my_string& operator=(const my_string& s);应该如何定义?

如果定义为

my_string& my_string::operator= (const my_string& s) {
	len = s.len;
	str = s.str;
	return *this;
}

在这里插入图片描述
会出现

  • a.str的动态内存没有释放,造成内存泄露
  • a和b指向同一个空间,操作任意一个对象都会修改字符串的值

那么定义为

my_string& my_string::operator= (const my_string& s) {  
	//先释放当前对象中的动态存储空间
	delete[] str;
	//再重新分配空间
	len = s.len;
	str = new char[len + 1];
	//最后进行字符串的拷贝
	strcpy(str, s.str);
	return *this;
}

此时如果操作a=a,同样有问题,因为a的空间已经被释放掉了
由此凸显了自赋值检测的重要性,正确代码为:

my_string& my_string::operator= (const my_string& s) {    
//赋值之前先进行自赋值检测
	if(this == &s) return *this;
	delete[] str;
	len = s.len;
	str = new char[len + 1];
	strcpy(str, s.str);
	return *this;
}	

重载下标运算符[ ]

  • 成员函数
  • 返回一个元素的引用,以便作左值

电脑蓝屏了,这没了

派生与继承

这定义没写

派生类的构造函数与析构函数

构造函数执行顺序:基类、子对象、派生类
析构函数执行顺序:派生类、子对象、基类
多继承和定义顺序有关,和赋值顺序无关

class A {
      public:
        A( ) {  
            a=0;
            cout<<"Default constructor called."<<a<<endl;
        }
        A(int i) {
            a=i;
            cout<<"Constructor called."<<a<<endl;
        }
        ~A( ) {  cout<<"Destructor called."<<a<<endl;  }
        void Print( )  {  cout<<a<<',';  }
        int Geta( ) {  return a;  }
      private:
       int a;
    };
class B:public A {
      public:
        B( ) {  
            b=0;
            cout<<"Default constructor called."<<b<<endl;
        }
        B(int i,int j,int k):A(i),aa(j) {
            b=k;
            cout<<"Constructor called."<<b<<endl;
        }
        ~B( ) {  cout<<"Destructor called."<<b<<endl;  }
        void Print( )        {
            A::Print( );
            cout<<b<<','<<aa.Geta()<<endl;
        }
      private:
        int b;
        A aa;
    };

signed main(){
	B bb[2];
    bb[0]=B(7,8,9);
    bb[1]=B(12,13,14);
    for(int i=0;i<2;i++)bb[i].Print();
	return 0;
}

输出

Default constructor called.0
Default constructor called.0
Default constructor called.0
Default constructor called.0
Default constructor called.0
Default constructor called.0
Constructor called.7
Constructor called.8
Constructor called.9
Destructor called.9
Destructor called.8
Destructor called.7
Constructor called.12
Constructor called.13
Constructor called.14
Destructor called.14
Destructor called.13
Destructor called.12
7,9,8
12,14,13
Destructor called.14
Destructor called.13
Destructor called.12
Destructor called.9
Destructor called.8
Destructor called.7
class A {
public:
    A(int i) {  	
        a=i;
        cout<<"Constructor 			called.A\n";
    }
    ~A()
    {  cout<<"Destructor 			called.A\n";  }
    void Print( )
    {  cout<<a<<endl;  }
private:
        int a;
};
class B {
public:
    B(int i) {
        b=i;
        cout<<"Constructor 		called.B\n";
    }
    ~B()
    {  cout<<"Destructor 		called.B\n";  }
    void Print()
    {  cout<<b<<endl;  }
private:
        int b;
};
class C {
 public:
    C(int i) {
        c=i;
        cout<<"Constructor called.C\n";
    }
    ~C()
    {  cout<<"Destructor called.C\n";  }
    int Getc( ) {  return c;  }
 private:
        int c;
 };
 class D :public A,public B {
 public:
        D(int i,int j,int k,int l):B(i),A(j),c(l) {
            d=k;
            cout<<"Constructor called.D\n";
        }
        ~D( )
        {  cout<<"Destructor called.D\n";  }
        void Print()
        {
            A::Print();
            B::Print(); cout<<d<<','<<c.Getc()<<endl;
        }
      private:
        int d;
        C c;
    };

signed main(){
	D d(5,6,7,8);
    d.Print();
    B b(2);
    b=d;
    b.Print();

	return 0;
}

输出

Constructor                     called.A
Constructor             called.B
Constructor called.C
Constructor called.D
6
5
7,8
Constructor             called.B
5
Destructor              called.B
Destructor called.D
Destructor called.C
Destructor              called.B
Destructor                      called.A

子类型

包含了类A的所有行为的类B,称为是A的子类型,B是适应于A,所有A的操作都可以用于B,就是说B的对象也是一种A,只不过比较特殊。
B可以给A赋值、引用赋值、地址值给A的指针赋值
这块有点绕,但是和后续的虚函数有关

    class A {
      public:
        A( )
        {  a=0;  }
        A(int i)
        {  a=i;  }
        void Print( )
        {  cout<<a<<endl;  }
        int Geta()
        {  return a;  }
      private:
        int a;
    };
class B:public A {
 public:
        B( ) {  b=0;  }
        B(int i,int j):A(i),b(j) {  }
        void Print( ) { 
            cout<<b<<',';
            A::Print();
        }
      private:
        int b;
    };
    void fun(A &a) {
        cout<<a.Geta( )+2<<',';
        a.Print();
    }

signed main(){
        A a1(10),a2;
        B b(10,20);
        b.Print();
        a2=b;
        a2.Print( );
        A *pa=new A(15);
        B *pb=new B(15,25);
        pa=pb;
        pa->Print();
        fun(*pb);
        delete pa;
	return 0;
}

虚基类

问题:调用不同基类中的相同成员时出现二义性
B派生出的B1和B2被C多继承
在这里插入图片描述
所以引入虚基类,用于有共同基类的场合,第一级就要用virtual修饰

class B{ public: int b;};
class B1 : virtual public B { private: int b1;};
class B2 : virtual public B { private: int b2;};
class C : public B1, public B2{ private: float d;};

signed main(){
	C  cobj;
	cobj.b;
	return 0;
}

在这里插入图片描述
在建造对象时,只有最远派生类调用虚基类的构造函数,其他中间的不调用,因此不会出现反复调用反复创建变量的情况,有助于cnt计数正常

虚函数

多态性指函数名字一样,但是具体操作不一样
多态性的实现:

  • 编译时多态 静态联编 不灵活 效率高

    函数重载
    运算符重载
    模板

  • 运行时多态 动态联编 灵活 效率低
    虚函数

virtual修饰函数,是虚函数,在基类里。派生类里对它进行函数超越/函数覆盖。
虚函数必须是成员函数,必须不是static。
包含虚函数的类是多态类。
虚函数仅限于继承,仅限于基类指针或引用调用,以指针所指的对象确定调用哪个版本,其他调用方式不是动态联编!!
构造函数不能是虚函数,但是析构函数可以。

虚析构函数

通过基类指针删除派生类对象

 class A {
 public: 
        virtual ~A()
        {  cout<<"A::~A() called.\n";  }
 };
 class B : public A {
      public:
        B(int i)
        {  buffer=new char[i];  }
        ~B( ) {  
           delete [] buffer;
           cout<<"B::~B() called.\n";
        }
 private:
        char *buffer;
 };
void fun(A *a) {
        delete a;
    }

signed main(){
    B *b=new B(5);
    fun(b);

	return 0;
}
B::~B() called.
A::~A() called.

设计C++程序时,一般在基类中定义处理某一问题的接口和数据元素,而在派生类中定义具体的处理方法。
通常将基类中处理问题的接口设计成虚函数,然后利用基类对象指针调用虚函数,从而达到单一接口多种功能的目的。

纯虚函数

在基类中声明但是不定义,函数值=0

virtual type func_name(parameter list) =0;

可以强制在派生类中重新定义虚函数。

抽象类

含有纯虚函数的类
不能说明抽象类的对象,但可以说明指针。抽象类不能用,只能当基类去派生。

模板 template

函数模板

template <class T1>
T1 maxx( T1 a, T1 b) {
    return a > b ? a : b;
}

signed main(){
	cout << "max(20, 30) = " << maxx(20, 30) << endl;
    cout << "max('t', 'v') = " << maxx('t', 'v') << endl;
    cout << "max(10.1, 15.2) = " << maxx(10.1, 15.2) << endl;
	return 0;
}

函数模板也能重载!参数不同即可,还能和普通函数一起重载,因为用这个模板,编译器实际上生成了一堆重载函数去用

类模板

template <class T1,class T2...class Tn> 
class 类模板名 {
	//类模板定义
}

n不宜过大
实例化可以搞出来类

typedef TStack<char> CStackChar;
typedef TStack<int>  CStackInt; 

其他重载、写函数等等和普通的操作一样

STL

我之后会写个专门的文章写c++标准模板库

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值