C++面向对象

c++17入门经典

chapter11 类

定义类

class ClassName

{

};

  1. 类的所有成员是默认私有的,public 和 private 将被应用到其后的所有成员山,除非有另一个访问修饰符

  2. 构造器头文件中声明有分号;方法也是;与c中的函数声明一样,只不过会放在一个类中

  3. 只有公共成员函数可以引用私有成员

  4. 如果没有初始化成员变量,将被默认为垃圾值

  5. 在创建对象时,要设置私有成员变量值,就必须给类添加一个特定类型的公共成员函数——构造函数

  6. 在调用默认构造函数时,可以不提供实参,编译器生成的默认构造函数没有参数,其唯一的作用是创建对象。

  7. if 没有给指针类型或基本类型的成员变量指定初始值,他们就会包含垃圾值

  8. 创建对象时,没有实体,仅仅是是对象,也需要构造器(空对象占用一个字节的内存空间)

  9. 在已经有含参数的构造器的基础上,想要定义一个函数体为空的构造函数,一般使用default关键字

    Box()=default;
    Box(){}//意思一样

  10. 可以在头文件中定义类,构造函数原型,成员函数原型,成员变量初始化

在源文件中引用头文件,在源文件中实现类中的方法,但是方法必须用类名来限定

// Box.h

ifndef BOX_H

define BOX_H

class Box
{
private:
double length{ 1.0 };
double width{ 1.0 };
double height{ 1.0 };
public:
Box(double lengthValue, double widthValue, double heightValue);
Box = default;
double volume();
};

endif // !BOX_H

// Box.cpp

include "Box.h"

include

//方法必须用类名来限定
Box::Box(double lengthValue, double widthValue, double heightValue)
{
cout << "box constructor called." << endl;
length = lengthValue;
width = widthValue;
height = heightValue;
}
double Box::volume()
{
return length * width * height;
}

note: 类的构造参数只有一个参数是有问题的,因为编译器啊,会把构造函数的参数自动转换为类类型

解决方法:explicit关键字修饰构造函数原型

//头文件

pragma once

class Cube
{
private:
double side;
public:
Cube(double aSide);
double volume();
bool hasLargerVolumeThan(Cube aCube);
};

//源文件

include "Cube.h"

include

using namespace std;

Cube::Cube(double aSide) :side{ aSide }
{
cout << "Cube one para = "< }
double Cube::volume()
{
return side * side * side;
}
bool Cube::hasLargerVolumeThan(Cube aCube)
{
return volume() > aCube.volume();//比较当前对象和参数对象的大小
}

//main函数

include

include"Cube.h"

using namespace std;

int main()
{
Cube box1{ 7.0 };
Cube box2{ 3.0 };
if (box1.hasLargerVolumeThan(box2))
{
cout << "box1 is larger than box2." << endl;
}
else
{
cout << "box1 is less or equal than box2." << endl;
}
cout << "volume of box1 is " << box1.volume() << endl;
//参数应该是个cube对象,编译器会将50编译成一个cube对象
if (box1.hasLargerVolumeThan(50.0))//这种情况就是参数类型转化为类类型的情况
{//box1并没有与体积是50进行比较,而是与边长为50的进行比较
cout << "volume of box1 is greater than 50 " << endl;
}
else
{
cout << "volume of box1 is less or equal to 50" << endl;
}
return 0;
}

解决方法:

//头文件中修改原型,

pragma once

class Cube
{
private:
double side;
public:
explicit Cube(double aSide);
double volume();
bool hasLargerVolumeThan(Cube aCube);
};

委托构造函数

一个构造函数的代码可以在初始化列表中调用同一个类中的同一个构造函数,

编译器不能区分带单个参数的构造函数,带三个参数但是后面两个参数被省略的构造函数

构造函数的分类

  • 无参构造

  • 有参构造

  • 普通构造

  • 拷贝构造

Person(const Person& p)
{
a = p.a;
...
}

构造函数的调用:

  • 括号法

    • Person p;//无参,调用无参构造时,不要写括号 编译器会认为这是函数的声明

    • Person p2(10);//有参

    • Person p3(p2);//拷贝

  • 显示法

    • Person p1; //无参

    • Person p2 = Person(10); //有参

    • Person p3 = Person(p2); //拷贝 不要用拷贝构造函数初始化匿名对象

  • 隐式转换法

    • Person p = 10;

    • Person p2 = p;

class Box
{
private :
double length{1};
double width{1};
double height{1};
public:
Box(double lv, double wv, double hv);
explicit Box(double side);
Box() = default;
double volume();
};

第一个构造函数实现:

Box::Box(double lv,double wv,double hv):length{lv},width{wv},height{hv}
{
cout<<"box constructor 1 callled."< }

第二个构造函数创建所有边长均相等的Box对象,他可以实现为

Box::Box(double side):Box{side ,side,side}
{
cout<<"box constructor 2 called"< }

第二个构造函数调用了第一个构造函数,即委托构造函数,将构造工作委托给另外一个构造函数

编译器提供三个默认构造函数:无参构造,有参构造,浅拷贝构造

(如果类中有指针成员变量时,可能会出现重复释放堆内存的情况)

深拷贝:解决指针指向同一堆地址的情况

副本构造函数&this指针

本质:复制已有对象创建新的对象

Box::Box(const Box& box):length{box.length},width{box.width},height{box.height}{}

副本构造函数仅仅创建副本 而不修改原对象,使用const &

访问私有类成员:

只读函数getXxx();

更新函数setXxx();

this指针:表示当前对象的指针,指向当前对象的地址(与java不同,this是指针而不是引用)

this指针的本质是指针常量(指针本身是不可以修改的,指向某一个对象后,不能指向其他了)

在成员函数后加一个const this后(本质修饰的是this),则this指向的值也不能修改了。两面封死

this指针隐藏在每一个调用成员函数的对象中 this->a = a;

XXX const YYY 指向的东西不能改*

*XXX const YYY 不能改指向

返回this指针的函数:(*this 就是当前对象的本体——链式编程思想,无限追加)

class Box
{
private:
double length{1.0};
double width{1.0};
double height{1.0};
public:
Box* setLength(double lv);
Box* setWidth(double wv);
Box* setHeight(double hv);
};

Box* Box::setLength(double lv)
{
if(lv>0)
length = lv;
return this;
}

Box* Box::setWidth(double wv)
{
if(wv>0)
width = wv;
return this;
}

Box* Box::setHeight(double hv)
{
if(hv)
height = hv;
return this;
}

Box myBox{3.0,4.0,5.0};
myBox.setLength(-20.0)->setWidth(40.0)->setHeight(10.0);
//返回this

同样除了返回this指针,还可以返回引用(类中的成员变量前,会隐式的加入this-> 所以空指针无法访问对象的成员变量)

比如:

Box& Box::setLength(double y)
{
if(lv>0)
length = lv;
return *this;
}

Box myBox{3.0,4.0,5.0};
myBox.setLength(-20.0).setWidth(40.0).setHeight(10.0);
//返回*this的引用,将成员函数的调用链接在一起,方法链

const对象和const成员函数

  1. const变量是不能修改其值的变量,同样,类类型也可以定义const变量——const对象

  2. const对象的任何成员变量都不能修改 mutable除外(const对象的任何变量本身是一个const变量,因而不能修改)

const对象不可以调用普通的成员,只能调用函数const

函数后const ——常函数 (本质:对this指针进行双重固定

//假设length属性是public
const Box myBox{3.0,4.0,5.0};
cout<<"the length of myBox is "< myBox.length = 2.0;//报错
//只能读不能写,常量是只读的

允许从const对象myBox读取成员变量,但是试图为这种成员变量赋值或已其他方式修改其值,会导致编译错误

  1. 这种原则也适用于指向const变量的指针和对const变量的引用

Box myBox{3.0,4.0,5.0};
const Box* boxPointer = &myBox;//落地在指针上
boxPointer->length = 2;//报错,因为是指向const变量的指针,所以不能通过指针修改常量的值

//这个别名是常量,不可以修改
void printBox(const Box& box);
//虽然作为实参传递的box对象不是const对象,但是printBox()无法修改该对象的状态

  1. const成员函数(常函数)在函数名后加一个const

    const Box myBox{3.0,4.0,5.0};
    cout<<"myBox dims are "< <<" by "< <<" by "<

    myBox.setLength(-20.0);//无法修改,报错
    myBox.setWidth(40.0);
    myBox.setHeight(10.0);
    //无法编译

    需要有一种方法来告诉编译器,可以调用const对象的哪些成员函数,

    解决方法:const成员函数

  • 把所有不修改对象的函数指定为const

    //源文件
    class Box
    {
    public:
    double volume() const;
    double getLength() const{return length;}//const成员函数
    double getWidth() const {return width;}
    double getHeight() const {return height;}

    void setLength(double lv){ if(lv>0) length=lv;}
    void setWidth(double wv){if(wv>0) width=wv;}
    void setHeight(double hv) {if(hv) height=hv;}
    

    }

  • 函数定义进行修改:

//头文件
double Box::volume() const
{
return lengthwidthheight;
}

对const myBox对象进行的调用都将工作,但仍然无法对const myBox对象调用setter函数

note: 对于const函数只能调用const成员函数,因此,应该将不修改对象的所有成员函数指定为const

const 正确性——放置const对象被修改的一系列限制

对于const对象,只能调用const成员函数。

const对象必须完全不可变,所以编译器只允许调用不会修改const对象的成员函数

试图在const成员函数内部修改对象的成员变量都会导致编译错误

将成员函数指定为const,实际上会使成员函数的this指针成为const指针

重载const

可以用const函数重载一个非const版本的成员函数

对于返回某个对象封装内部数据的一部分数据的指针或引用的函数,常常进行重载

例子

class Box
{
private:
double _length{1.0};
double _width{1.0};
double _height{1.0};
public:
double& length(){return _length;}//可以加上const,就变成了专用于const版本的成员函数
double& width(){return _width;}//添加const之后,因为返回的double是基本类型,可以将&去掉
double& height(){return _height;}
}

使用成员函数

Box box;
box.length() = 2;//将length值改为2返回别名_length=2;
//一种用来替换getter setter函数的方法

类的对象数组

  • 主函数

#include

include"Box.h"

using namespace std;

int main()
{
const Box box1{ 2,3,4 };
Box box2{ 5 };
cout << "box1 volume=" << box1.volume() << endl;//1called-24
cout << "box2 volume=" << box2.volume() << endl;//12called-125
Box box3{ box2 };
cout << "box3 volume=" << box3.volume() << endl;//copycalled 125
cout << endl;
Box boxes[6]{ box1,box2,box3,Box{2} };
//前三个复制123box 第四个调用12构造,后面默认构造
return 0;
}

  • 头文件

#pragma once

ifndef BOX_H

define BOX_H

include

class Box
{
private:
double length{ 1.0 };
double width{ 1.0 };
double height{ 1.0 };

public:
Box(double ly, double wv, double hv);
Box(double side);
Box();
Box(const Box& box);
double volume() const;
};

endif // !BOX_H

源文件

#include "Box.h"

include

using namespace std;

Box::Box(double lv, double wv, double hv) :length{ lv }, width{ wv }, height{ hv }
{
cout << "Box constructor 1 called." << endl;
}

Box::Box(double side) : Box{ side,side,side }
{
cout << "Box constructor 2 called" << endl;
}

Box::Box()
{
cout << "Default constructor called" << endl;
}

Box::Box(const Box& box) :length{ box.length }, width{ box.width }, height{ box.height }
{
cout << "Box copy constructor called" << endl;
}

double Box::volume() const
{
return length * height * width;
}

类的静态成员

无论是否声明了对象都可操作静态成员

和java中的static类似

//头文件
class Box
{
private:
double length{1};
double width{1};
double height{1};
static inline size_t objectCount{};//内联静态变量,类外无法访问
public:
Box(double lv,double wv,double hv);
Box(double side);
Box();
Box(const Box& box);
double volume() const;
size_t getObjectCount() const{return objectCount;}//获取内联静态变量
};

//源文件

include "Box.h"

include

using namespace std;

Box::Box(double lv, double wv, double hv) :length{ lv }, width{ wv }, height{ hv }
{
objectCount++;
cout << "Box constructor 1 called." << endl;
}

Box::Box(double side) : Box{ side,side,side }
{
cout << "Box constructor 2 called" << endl;
}

Box::Box()
{
++objectCount;
cout << "Default constructor called" << endl;
}

Box::Box(const Box& box) :length{ box.length }, width{ box.width }, height{ box.height }
{
++objectCount;
cout << "Box copy constructor called" << endl;
}

double Box::volume() const
{
return length * height * width;
}

main函数

#include

include"Box.h"

using namespace std;

int main()
{
const Box box1{ 2,3,4 };
Box box2{ 5 };
cout << "box1 volume=" << box1.volume() << endl;//1called-24
cout << "box2 volume=" << box2.volume() << endl;//12called-125
Box box3{ box2 };
cout << "box3 volume=" << box3.volume() << endl;//copycalled 125
cout << endl;
Box boxes[6]{ box1,box2,box3,Box{2} };
cout< cout<<"there are now "< //可以使用任意一个对象访问static变量,结果都是9个对象
cout<<"there are now "< return 0;
}

上面程序表明,只有静态成员objectCount一个副本,所有构造器都更新了它,

note: 即使在类的定义中添加了objectCount,Box对象的大小也不变,因为静态成员变量不是任何对象的一部分,他们属于类

访问静态成员变量

if将objectCount声明为public,就可以直接在main函数中访问其值,不需要函数

即使没有创建对象,静态变量也存在 类名::静态变量

静态常量

c++17使用内联变量初始化静态常量

通常会将定义为static const的所有变量也定义为inline,因此,无论成员变量的类型是什么,都可以在类定义中进行初始化

inline:建议编译器将指定的函数体插入并取代每一处调用该函数的地方(上下文),从而节省了每次调用函数带来的额外时间开支

if 在源文件中定义这种成员变量,则必须省略inline关键字

class Cylind{
public://定义四个静态常量,声明为public无大碍
static inline const float maxRadius{35};
static inline const float maxHeight{60};
static inline const string defaultMaterial{"paperboard"};
Cylind(double radius,double height,string_view material = defaultMaterial);
float volume() const;
private:
static inline const float PI{3.1415926};
float radius ;
float height;
string_view material;
}

静态函数

在头文件中直接写实现,调用的时候使用类名::函数名调用

static void printCount(){cout<<"count = "<

运算符重载

头文件中声明运算符函数(也可以实现)

头文件中使用内联函数直接实现运算符函数的功能

bool operator<(const Box& box) const;

inline bool operator<(const Box& box) const
{
//内联函数
}

  • explicit 修饰构造函数时,可以防止隐式转换和复制初始化(防止出现 Person p = 10; 来构造对象)

  • explicit 修饰转换函数时,可以防止隐式转换,但按语境转换除外

如果使用了using语句,可以改变他们的可访问性

  • 成员函数重载

A operator+()(A& a)
{
this->x = this->x+a->x;
return (*this);
}

  • 全局函数重载

A operator+(A& a ,A& b)
{
A temp;
temp.x = a.x+b.x;
return temp;
}

  • 利用全局函数重载<<运算符

//一般将这个函数声明其他类中的首行
//作为友元函数
ostream& operator<<(ostream& stream,A& a)
{
stream<<" "< return stream;
}

  • 前置与后置递增

//前置++ ++num
A& operator++()
{
num++;
return *this;//前置递增一定返回引用
}

//后置++ num++
A operator++(int)
{
A temp = *this;
num++;
return temp;//后置返回一个值,因为是局部的,引用会失效
}

  • 赋值运算符重载

A& operator=(A& a)
{//x是成员指针变量
if(x != Null )//清理堆区内存
{
delete x;
x = NULL;
}
x = new int(*a.x);//深拷贝
return *this;
}

友元friend

可以访问类中的私有成员

friend友元提供了一种普通函数或者类成员函数 访问另一个类中的私有或保护成员 的机制。也就是说有两种形式的友元:

(1)友元函数:普通函数对一个访问某个类中的私有或保护成员。

在想要公开的私有成员的类中声明friend 类名::友元函数() ,此时这个其他对象的这个友元函数可以访问这个类中的私有成员

friend void GoodGay::visit();//这个函数可以访问当前类中的私有成员

(2)友元类:类A中的成员函数访问类B中的私有或保护成员

在想要公开自己的类中声明friend 把允许的访问的类拽进来

friend void func();

优点:提高了程序的运行效率。

缺点:破坏了类的封装性和数据的透明性。

总结: - 能访问私有成员 - 破坏封装性 - 友元关系不可传递 - 友元关系的单向性 - 友元声明的形式及数量不受限制

Carton carton;//先调用父类的无参构造器,在调用自己的无参构造器
Carton candyCarton{ 50,30,20 ,"thin cardboard" };//先调用父类的三参构造器,再调用自己的4参构造器
cout << "carton volume is " << carton.volume() << endl;//默认为1
cout << "candyCarton volume is" << candyCarton.volume() << endl;

继承

三种继承方式:将父类成员变成以下三种访问形式

  • 公共继承

  • 保护继承(类外访问不到)

  • 私有继承

子类中有同名的普通成员函数,会隐藏父类的成员。如果还想使用,需要使用作用域

而对于静态成员,可以使用类名去访问getxxx

菱形继承:子类继承了多份数据,防止资源浪费,使用虚拟继承。

virtual zzzzzzz getxxx(大品牌)

关于多态

虚函数的调用取决于指向或者引用的对象的类型,而不是指针或者引用自身的类型。

虚函数是多态的基础,运行时才确定(子类要重写父类的虚函数+父类指针指向子类对象)

  1. C++中的多态:在C++中会维护一张虚函数表,根据赋值兼容规则,我们知道父类的指针或者引用是可以指向子类对象的。

  2. explicit 修饰构造函数时,可以防止隐式转换和复制初始化

  3. explicit 修饰转换函数时,可以防止隐式转换,但按语境转换除外

  4. 如果一个父类的指针或者引用调用父类的虚函数则该父类的指针会在自己的虚函数表中查找自己的函数地址,如果该父类对象的指针或者引用指向的是子类的对象,而且该子类已经重写了父类的虚函数,则该指针会调用子类的已经重写的虚函数。

  5. 在函数体中,可以使用引用参数调用基类的虚函数,获得多态性行为。

  6. 如果想实现多态性,必须总是使用指针或引用(不能将对象直接添加到vector中,这会让所有的对象都强制转换为父类对象,无法实现多态)

  7. 智能指针可以安全的使用指针,多态性得以保留,可以提供多态行为

  8. 为了确保为自由存储区中创建的派生类对象调用正确的析构函数,需要使用虚析构函数,只需要在基类的析构函数声明中添加关键字virtual(告诉编译器,通过指针或引用参数调用的析构函数应是动态绑定的,这样析构函数就在运行期间选择)

  9. 推荐使用default关键字来声明析构函数。编译器生成的析构函数不是虚析构函数;

  10. 当使用多态性时,类中必须有一个虚析构函数用来确保能够正确的释放对象。

  11. 纯虚数:在基类中定义的一个操作,在父类中不提供有意义的实现代码,主要作用就是允许函数的派生类版本进行多态性的调用(加上=0)。(一般的虚函数是可以在父类实现的)

  12. 纯虚数一定出现在抽象类中,抽象类不允许创建实例,构造器保护。

异常

throw trouble;//抛出来的对象,使用catch抓住(设计对应的catch,或者使用基类接收)

catch (const MyTroubles& t)//用父类接受,使用多态性,展现出错误的具体情况(虚函数在基类,子类复制虚函数中参数的变化)

noexcept指定函数不会抛出异常

类模板

include

include

template

template class ClassName
{
参数类型 非参数类型 实参
};

你可能感兴趣的:(C++面向对象)