面向对象和面向过程
面向过程:分而治之,大问题分解为小问题,一个一个解决。
面向对象:抽象,把数据和函数抽象为属性和行为,封装到一类对象里。
面向对象
成员变量一般都是private,函数方法(接口)一般都是public
构造函数作用:对对象的成员进行初始化。可以重载。如果没有写,系统会默认生成一个无参无内容的构造函数。
按形式:无参、有参
按功能:普通、拷贝
自己不屑拷贝构造函数时,编译器会默认提供,复制全部成员。拷贝构造函数的参数不可以写自身的类,否则会引发递归,应该用引用。
析构函数:对象销毁的时候,会执行,来释放资源。
创建对象时,先调用父类的构造函数,销毁对象时,先调用子类的析构函数。
栈中创建的对象,后创建的先销毁。
返回值优化:RVO(Return Value Optimization)\ Release版本NRVO(Named Return Value Optimization)
-fno-elide-constructors(取消这个优化)

深拷贝和浅拷贝
浅拷贝:直接复制变量的值。
深拷贝:重新申请一个内存,放入拷贝的值。
初始化列表语法
class person{
private:
int m_a;
double m_b;
char m_c;
const int m_d;
public:
person(int a, double b, char c, int d) :
m_a(a),
m_b(b),
m_c(c),
m_d(d){
}
};
/*
对于const这种,不可以在构造函数内赋值,可以通过列表初始化赋值
*/
类对象作为类成员的构造顺序和类字段的顺序一致,析构顺序和字段顺序相反。
静态成员
所有类共享这一个成员。
静态数据只能在类内声明,类外定义。
静态函数不能访问类内非静态数据。
单例设计模式
定义:一个类的对象在一个程序中只能存在一个。
方法:
- 私有化默认构造函数
- 创建一个静态实例
- 提供静态获得实例的方法
- 懒汉式
- 饿汉式
//饿汉式:用的时候直接返回
class person{
private:
person(){
}
static person* onePerson;
public:
static person* getInstance(){
return onePerson;
}
};
person* person::onePerson = new person;
//懒汉式:第一次用的时候才创建,再返回
class person{
private:
person(){
}
static person* onePerson;
public:
static person* getInstance(){
if (onePerson == nullptr){
onePerson = new person;
}
return onePerson;
}
};
person* person::onePerson = nullptr;
常函数和常对象
class person{
private:
int m_a;
double m_b;
char m_c;
mutable int e;
const int m_d;
public:
person(int a, double b, char c, int d) :
m_a(a),
m_b(b),
m_c(c),
m_d(d){
}
//show不可以通过this修改对象的数据
//此时想修改某些数据,可以在数据前加mutabel
void show() const {
e = 100;
}
};
person p;
const p;
//p的内容不可修改
常对象可以调用常函数,不可以调用普通函数。
友元
友元函数
想让函数访问类的私有成员,可以在类的内部加入如下函数声明
friend returnType func(agr1, ...);
友元类
其他类想访问该类的私有成员,可以在该类中加入其他类的声明
friend class classname;
友元成员函数
其他类的成员函数想访问该类的私有成员,可以在该类中加入其他类的类的成员函数的声明
friend returnType classname::func(arg1, ...);
运算符重载
//类内
returnType operator+(classType& a){
//...
return res;
}
//全局
returnType operator+(classType& a, classType& b){
//...
return res;
}
t1 + t2;
//t1.operator+(t2);
内存泄漏:用完内存后,忘记释放,导致这块内存不能再使用。
智能指针:自动销毁、释放空间。
编译器默认给一个类添加四个函数
- 构造函数(空)
- 析构函数(空)
- 拷贝构造函数(浅拷贝)
- operator= 重载函数(浅拷贝)
函数调用运算符重载
returnType operator()(classType& a){
//...
return res;
}
class person{
public:
person(){
}
void operator()(){
cout << "hello world!" << endl;
}
};
person p;
//p.operator()();
p();
//仿函数,也成为函数对象,本质是对象,而不是函数
运算符重载可以参考字符串类封装
类的自动类型转换和强制类型转换
class person{
private:
int m_a;
double m_b;
char m_c;
mutable int e;
const int m_d;
public:
//这时候 preson p = 10;就会报错
explicit person(int a){
m_a = a;
}
person(int a, double b, char c, int d) :
m_a(a),
m_b(b),
m_c(c),
m_d(d){
}
//show不可以通过this修改对象的数据
//此时想修改某些数据,可以在数据前加mutabel
void show() const {
e = 100;
}
};
类的继承
继承为了代码复用。
class person{
};
class teacher : person{
};
好处:
- 复用
- 维护
- 多态的前提
坏处:耦合性增加。好的开发:高内聚,低耦合
内聚:自己完成一件事的能力。
- public 公开
- private只能本类访问
- protected 子类可以访问,类外不能访问
不同继承,会成为派生类的什么成员
- 公有继承,不改变
- 保护继承,公有的会变保护,不能访问父类私有
- 私有继承,公有和保护会变成私有,不能访问父类私有
没有写继承方式,默认私有。
子类将父类中所有成员都继承了,包括私有的。
VS command prompt工具查看类:
- 打开当前原文件目录
- cl /d1 reportingSingleClassLayout 类名 文件名
子类默认调用父类的无参构造函数。如果父类手动声明了有参构造函数,编译器不会为其自动生成无参构造函数。
子类调用父类方法
son.Base::func();
如果子类中出现父类同名函数,子类会隐藏父类中所有同名函数。(静态成员函数同)
多(重)继承
成员变量和方法容易重名产生歧义。
菱形继承和虚继承

虚继承
class man : virtual public person{
};
class singer : virtual public person{
};
class singerMan : public singer, public man{
};
vbptr: virtual base pointer(虚基类指针)
虚继承原理:只有一个唯一的成员,通过保存虚基类指针,这个指针指向的是一张表(虚基类表),这个表保存了当前获取到唯一的数据的偏移量。

对付类的virtual函数继承后重新编写,称为重写、覆写。实现了动态多态。
//不用virtual,子类重名函数的效果
animal aml = new cat();
animal aml1 = new dog();
aml.Speak();
aml1.Speak();
//aml speak
//aml speak
//原因,检查了animal的类型,就会去静态链接
//用virtual后重写,会动态链接,实现了多态
animal aml = new cat();
animal aml1 = new dog();
aml.Speak();
aml1.Speak();
//cat speak
//dog speak
多态:
- 静态多态:函数重载、运算符重载等
- 动态多态:虚函数重写,父类指针指向子类对象(地址晚绑定、动态多态)
多态前提:
- 继承关系
- 虚函数
- 重写
虚函数机制:
- 虚基类表,记录了一些成员的地址
- 发生重写,记录子类的,否则记录基类的
- vftable记录函数的 vfptr
类中有纯虚函数就是抽象类。抽象类不能创建对象。子类必须重写纯虚函数,否则子类也是抽象类。
class person{
public:
virtual void speak()=0;
}
虚析构和纯虚析构
#include <iostream>
#include <cstring>
using namespace std;
class animal{
public:
animal(){
cout << "animal 构造函数调用了" << endl;
}
virtual void speak()=0;
~animal(){
cout << "animal 析构函数调用了" << endl;
}
};
class cat : public animal{
char* m_name;
public:
void speak(){
cout << "miao" << endl;
}
cat(const char* name){
cout << "cat 构造函数调用了" << endl;
this->m_name = new char[strlen(name) + 1];
strcpy(this->m_name,name);
}
~cat(){
cout << "cat 析构函数调用了" << endl;
if (this->m_name != nullptr){
delete[] this->m_name;
this->m_name = nullptr;
}
}
};
int main()
{
animal* c = new cat("smallcat");
delete c;
return 0;
}
//这种情况没有调用cat构造函数直接调用了父类的析构函数
//解决办法
class animal{
public:
animal(){
cout << "animal 构造函数调用了" << endl;
}
virtual void speak()=0;
virtual ~animal(){
cout << "animal 析构函数调用了" << endl;
}
};
/*
纯虚析构必须在类外写实现
纯虚析构的目的就是让类为抽象类
*/
原理:父类加了virtual之后的析构函数,子类继承后,用父类变量指向子类对象时会有 vfptr指向vftable,vftable存放子类的析构函数的偏移地址。
类似于父类的函数,子类有同名函数,当父类指针指向子类对象的时候,会调用父类的函数,而不是调用子类的同名函数,要想调用子类的函数(即实现多态)就得在父类中声明此函数为虚函数(加 virtual)。
|