第13章:运算符重载
浅复制和深复制:同类对象之间可以通过赋值运算符“=”互相赋值。如果没有经过重载,“=”的作用就是把左边的对象的每个成员变量都变得和右边的对象相等,即执行逐个字节赋值的工作,这种复制称为浅复制。深复制:经过重载,赋值号=的功能,不再是浅复制,而是将一个对象中指针成员变量指向的内容,复制到另一个对象中指针成员变量指向的地方去。这样的复制称为深复制。
复制构造函数是构造函数的一种,也称为拷贝构造函数,它只有一个参数,参数类型是本类的引用。
回顾复制构造函数的一些内容:复制构造函数被调用的三种情况
- 当用一个对象去初始化同类的另一个对象时,会引发复制构造函数被调用
- 如果函数F的参数是类A的对象,那么当函数F被调用时,类A的复制构造函数将被调用。
- 如果函数的返回值是类A的对象,则函数返回时,类A的复制构造函数被调用。
还是很有必要对一些书写的代码在博客上进行记录的,
实现了一个长度可变的整形数组类,但是这个类的实现过程中,每次在数组尾部添加一个元素都要重新分配内存并且复制原有内容,显然是效率低下的。有什么办法能够加快添加元素的速度呢?(这一个代码用的就是“配置新空间-数据移动-释放旧空间”)
#include <iostream>
#include <vector>
#include <algorithm>
#include <string.h>
using namespace std;
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 Complex::operator-(const Complex&c){
return Complex(real-c.real,imag-c.imag);
}
class CMaster;
class CDog
{
CMaster *pm;
};
class CMaster{
CDog dogs[10];
int dogNum;
};
int findtarget(vector<int>& pushed,int target){
vector<int>::iterator it=find(pushed.begin(),pushed.end(),target);
return it-pushed.begin();
}
//编写一个长度可变的字符串类,char* 类型的成员变量用以指向动态分配的存储空间
/*class String{
private:
char* str;
public:
String ():str(NULL) {}
const char *c_str() {return str;};
String& operator =(const char *s);
~String();
};
String & String::operator= (const char*s){
if(str)
delete [] str;
if(s){
str=new char[strlen(s)+1];
strcpy(str,s);
}
else{
str=NULL;
}
return *this;
}
String::~String(){
if(str)
delete[] str;
}
String & String::operator= (const String & s){
if(str==s.str){
return *this;
}
if(str)
delete[] str;
if(s.str){
str=new char[strlen(s)+1];
strcpy(str,s.str);
}
else{//如果s.str==NULL
str==NULL;
}
return *this;
}*/
class CArray{
int size;//数组元素的个数
int *ptr;//指向动态分配的数组
public:
CArray (int s=0);
CArray(const CArray &a);
~CArray();
void push_back(int x);
int length () {return size;};
CArray& operator =(const CArray &a);
int & operator[] (int i){
return ptr[i];
}
};
CArray::CArray(int s): size(s){
if(s==0) ptr=NULL;
else{
ptr=new int[s];
}
}
CArray::CArray(const CArray &a){
/*if(a.size==0){//这一块写的有问题,感觉没有理解复制构造函数的作用
size=0;
delete []ptr;
ptr=NULL;
}*/
if(a.ptr==NULL){
ptr=NULL;
size=0;
}
else{
size=a.size;
delete[] ptr;
ptr=new int[size];
memcpy(ptr,a.ptr,sizeof(int)*a.size);
}
//return *this; 不该有这一句,复制构造函数没有返回值,也不是void
}
CArray::~CArray(){
if(ptr){
delete[] ptr;
}
}
//赋值号的作用是使得=左边对象中存在的数组,大小和内容都和右边的对象一样;要防止a=a这样的出错
CArray& CArray::operator=(const CArray& a){//可分为如果a里面的数组是空的;如果原有空间足够大,就不用分配新的空间
if(a.ptr==ptr){
return *this;
}
if(a.ptr==NULL){
if(ptr){
delete[] ptr;
}
ptr=NULL;
size=0;
return *this;
}
else{
if(a.size>size){
if(ptr){//ptr不为空
delete[] ptr;
}
ptr=new int[a.size];
}
memcpy(ptr,a.ptr,sizeof(int)*a.size);
size=a.size;
}
return *this;
}
void CArray::push_back(int x){
if(ptr){
//重新分配空间,复制原数组的内容
int* temp=new int[size+1];
memcpy(temp,ptr,sizeof(int)*size);
delete[] ptr;
ptr=temp;
ptr[size++]=x;
}
else{
ptr=new int[1];
ptr[size++]=x;
}
}
int main()
{
//String s;
//s="Good Luck,";
//cout<<s.c_str()<<endl;
//s="Shenzhou 8!";
//cout<<s.c_str()<<endl;
CArray a;
for(int i=0;i<5;++i){
a.push_back(i);
}
CArray a2,a3;
a2=a;
for(int i=0;i<a2.length();++i)
cout<<a2[i]<<" ";
cout<<endl;
a[3]=100;
CArray a4(a);
for(int i=0;i<a4.length();++i)
cout<<a4[i]<<" ";
return 0;
}
第14章:继承与派生
基类的成员自动成为派生类的成员。
构造函数回顾:面向对象的程序设计语言倾向于对象一定要初始化后,使用起来才比较安全,因此引入了构造函数的概念,用于对对象进行自动初始化。其名字和类的名字一样,不写返回值类型,可以重载,即一个类可以有多个构造函数。
析构函数回顾:析构函数在对象消亡时即自动被调用。可以定义析构函数在对象消亡前做善后工作,
封闭类:一个类的成员变量如果是另一个类的对象,就称为“成员对象”。包含成员对象的类,称为封闭类。
protected:保护成员的可访问范围比私有成员大,比公有成员小。能访问私有成员的地方都可以访问保护成员。保护成员扩大的访问范围表现在基类的保护成员可以在派生类的成员函数中被访问。??引入保护成员的理由是基类的成员本来就是派生类的成员,因此对于那些出于隐藏的目的,不宜设为公有,但又确实需要在派生类的成员函数中经常访问的基类成员。注意:在派生类的成员函数中,只能访问成员函数所作用的那个对象的基类保护成员。
多层次的派生:派生类对象生成时,会引发一系列构造函数调用,顺序是先从上至下执行所有基类的构造函数,再按照成员对象的定义顺序执行各个成员对象的构造函数,最后执行自身的构造函数。
注明:会从最顶层的基类开始逐层往下执行所有基类的构造函数,最后再执行自身的构造函数
包含成员对象的派生类:
派生类的成员包括派生类自己定义的成员,直接基类中定义的成员,以及所有间接基类的全部成员。和封闭类交代成员对象如何初始化类似,派生类交代基类对象如何初始化,也需要在派生类构造函数后面添加初始化列表,
公有派生的赋值兼容规则:一般情况下都使用公有派生9
1)派生类对象可以赋值给基类对象
2)派生类对象可以用来初始化基类引用
3)派生类对象的地址可以赋值给基类指针,也即派生类的指针可以赋值给基类的指针
class A
{
};
class B: public A{
};
int main(){
A a;
B b;
a=b;//派生类对象赋值给基类对象
A & r=b;
A * pa=&b;
B * pb;=&b;
pa=pb;
return 0;
};
14.8 基类和派生类的指针的互相转换
在公有派生的情况下,派生类的指针可以直接赋值给基类指针。但即便基类指针指向的是一个派生类的对象,也不能通过基类指针访问基类没有的而派生类中有的成员。
访问静态成员时,则可以通过类名::成员名的方式访问.
复习回顾:
- 面向对象程序设计方法具有抽象 封装(数据和对数据的处理封装在一起?) 继承 多态。在成员函数前面有inline关键字或者函数体写在类定义内部的成员函数,是内联成员函数。
- 尽可能使用const
char greeting[]="Hello"; char* p=greeting; const char* p=greeting;//non-const pointer const data char* const p=greeting; const char* const p=greeting;
11章的一些回顾:private用来制定能够私有成员,一个类的私有成员,不论是成员变量还是成员函数都只能在该类的成员函数内部才能被访问。注意下面的a.v=3出错的这一句。
#include <bits/stdc++.h> using namespace std; class student{ private: string name; int age; string No; int grade1; int grade2; int grade3; int grade4; public: void print(); void Init(const string name_,int age_,const string No_,int grade1_,int grade2_,int grade3_,int grade4_); }; //语句放在函数体外(main与其他函数) void student::Init(const string name_,int age_,const string No_,int grade1_,int grade2_,int grade3_,int grade4_){ name=name_; age=age_; No=No_; grade1=grade1_; grade2=grade2_; grade3=grade3_; grade4=grade4_; } void student::print(){ int avg=(grade1+grade2+grade3+grade4)/4; std::cout<<name<<","<<age<<","<<No<<","<<avg<<endl; } int main() { //student r; //r.Init("Tom",19,"2015",80,70,90,100); //r.print(); /*class A{ public: int v; A* p; }; A a; a.p=new A;//?? delete a.p; return 0;*/ class A{ int v; }; A a; a.v=3;//出错 return 0; }
- protected访问范围说明符:能访问私有成员的地方,都能访问保护成员,保护成员扩大的访问范围表现在基类的保护成员可以在派生类的成员函数中被访问
第15章:多态与虚函数
多态:多态就是不同继承类的对象,对同一消息做出不同的响应,基类的指针指向或绑定到派生类的对象,使得基类指针呈现不同的表现方式(这里是不是少说了一种基类引用的方式?)。在基类的函数前加上 virtual 关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/cpp-interview-highlights/ejfxid/
实现方法:多态是通过虚函数实现的,虚函数的地址保存在虚函数表中,虚函数表的地址保存在含有虚函数的类的实例对象的内存空间中。
实现过程:
1.在类中用virtual关键字声明的函数叫做虚函数
2.存在虚函数的类都有一个虚函数表,当创建一个该类的对象时,该对象有一个指向虚函数表的虚表指针
3.当基类指针指向派生类对象,基类指针调用虚函数时,基类指针指向派生类的虚表指针,由于该虚表指针指向派生类虚函数表,通过遍历虚表,寻找相应的虚函数。
多态是指同一名字的事物可以完成不同的功能。
1.通过基类指针实现多态
2.通过基类引用实现多态