OPP特性:抽象、封装和数据隐藏、多态、继承、代码的可重用性
抽象和类
将问题的本质抽象出来,并根据特征来描述解决方案。
类规范有两部分组成:
1 类声明:以数据成员的方式描述数据部分,以成员函数(方法)的方式描述公有接口。
2 类方法定义:描述如何实现类成员函数。
类声明提供了类的蓝图,方法定义则提供了细节。
接口: 接口是一个共享框架;对于类,我们说公共接口,公共(Public)是使用类的程序,交互系统由类对象组成,接口由编写类的人提供的方法组成。接口让程序员能够编写与类对象交互的代码让程序能使用类对象。
C++中通常将接口(类定义)的定义放在头文件中,并将实现(类的方法的代码)放在原文件中,
访问控制:public 和private描述了对类成员的访问控制,程序可以访问对象的公有部分,只能通过公共部分(或者友元函数)访问对象的私有成员。防止程序直接访问数据,被称为数据隐藏。
关键字protected被用在类继承时,后边讨论。
类的设计应尽可能的将公有接口与类的实现细节分开,公有接口表示设计的抽象组件。将实现细节放在一起并将他们与抽象分开被称为封装。数据隐藏(将数据放在私有部分)是一种封装。将实现细节隐藏在私有部分(私有函数),也是一种封装。封装的另一个例子是,将类函数定义和声明放在不同的文件中。
Private可以省略,类中,成员默认是私有的,只有在public中的才是共有,对外接口。注意结构体默认是public的。
类成员函数:
两个特殊特征:使用作用域解析符(::)标记成员函数;类的方法可以访问private组件
void stock::update(doule price);
内联函数:
定义位于类声明中的函数将自动成为内联函数,短小的成员函数可以作为内联函数。
类声明之外也可以显式的定义函数未内联函数, 使用关键字inline:
inline void Stock::set_tot(){
...
}
内联函数要求在每个使用它的文件中都要对其进行定义,所以最简单方法是内联函数放在定义类的头文件中。
每个对象都有自己的内存空间,保存着自己的内部变量和类成员。同一个类的所有对象共享类方法,每种方法只有一个副本。不同的对象的相同方法执行同一个代码块,只是将这些代码用于不同的数据。
#ifndef __SLT_ENG_H__
#define __SLT_ENG_H__
void start_slt_eng();
class Capital {
private:
int my_total_funds;
public:
Capital();
void store(int funds);
void spend(int funds);
void show();
~Capital();
};
#endif //SLT_ENG
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include "slt_eng.h"
using namespace std;
void start_slt_eng() {
printf("start_slt_eng!\n");
Capital mycapital(300, "zhangsan");
mycapital.show();
mycapital.store(100);
mycapital.show();
mycapital.spend(20);
cout << "end spend" <<endl;
cout << "mycapital" << &mycapital <<endl;
Capital *licapital = new Capital(200, "wangwu");
licapital->show();
licapital->store(100);
licapital->show();
cout << "spend 30" <<endl;
licapital->spend(30);
cout << "mycapital" << licapital <<endl;
delete licapital;
}
Capital::Capital() {
cout << "gouzao: Capital"<< endl;
my_total_funds = 0;
}
Capital::Capital(int funds, const char* name) {
cout << "gouzao: Capital"<< endl;
my_total_funds = funds;
this->name = name;
}
void Capital::show() {
cout << name << " funds:" << my_total_funds << endl;
}
void Capital::spend(int funds) {
cout << name << " before spend my_total_funds:" << my_total_funds << endl;
my_total_funds -= funds;
cout << name << " after spend my_total_funds:" << my_total_funds << endl;
}
void Capital::store(int funds) {
cout << name << " before store my_total_funds:" << my_total_funds << endl;
my_total_funds += funds;
cout << name << " after store my_total_funds:" << my_total_funds << endl;
}
Capital::~Capital() {
cout << "xigou: Capital :"<< this << endl;
my_total_funds = 0;
}
输出结果:
start_slt_eng!
gouzao: Capital
my_total_funds:0
before store my_total_funds:0
after store my_total_funds:100
my_total_funds:100
before spend my_total_funds:100
after spend my_total_funds:80
end spend
mycapital0x62fd00
gouzao: Capital
licapital
my_total_funds:0
before store my_total_funds:0
after store my_total_funds:100
my_total_funds:100
licapital spend 30
before spend my_total_funds:100
after spend my_total_funds:70
mycapital0x3522f80
xigou: Capital :0x3522f80
xigou: Capital :0x62fd00
end main
OOP的设计常遵从客户/服务器模型,服务器即类的声明与定义,客户端就是实现类的程序。
服务端不应该修改Public接口,修改内部实现对客户端不会产生影响。
构造函数和析构函数
构造函数目的是让使用类对象像使用标准的类型一样,
int age = 20;
struct st_student {
char name[20];
int age;
int high;
};
struct st_student stu1 = {"zhangsan", 10, 168};
struct st_student stu2 = {"lisi", 17, 178};
struct st_student stu3 = {"wangwu", 18, 188};
class st_student {
public: //内部变量如果是public的就可以像struct一样用初始化列表{}初始化
char name[20];
int age;
int high;
};
st_student stu1 = {"zhangsan", 10, 168}; // 编译器按照列表位置进行初始化,例如10 对应第二个变量age
st_student stu2 = {"lisi", 17, 178};
st_student stu3{
"wangwu",
18,
188
};
不能像常规变量那样初始化的原因就是类中变量是private的,外部无法访问,如果声明为public,就可以了。struct可以的原因就是结构体默认变量类型为public。
所以构造函数必须是public的,值得注意的是构造函数没有返回类型
Capital::Capital(int funds, const char* name);
可以使用默认参数:
Capital::Capital(int funds, const char* name = "defaultName");
构造函数的参数,不能和类成员变量名相同,可以参考下边写法。
Capital::Capital(int funds, const char* name) {
cout << "gouzao: Capital"<< endl;
m_my_total_funds = funds;
m_name = name;
}
Capital::Capital(int funds, const char* name) {
cout << "gouzao: Capital"<< endl;
my_total_funds_ = funds;
name_ = name;
}
构造函数的使用
显示使用:
Capital mycap = Capital(100, "zhangsan");
隐式使用
Capital myCapital(100, "zhangsan");
Capital *pmyCapital = new Capital(100, "zhangsan");
默认构造函数
默认构造函数是在未显式初始值时使用的构造函数:例如:Capital myCapital;
值的注意的是只有当没有构造函数时候,编译器才会自动生成默认构造函数,当有任何一个构造函数时候,需要显式写出构造函数:
Capital capital1; 此种定义才是使用默认构造函数,
Capital capital1();这种写法是错误的,不能实例化对象。是一个函数声明。
Capital::Capital() {
cout << "moren gouzao: Capital"<< endl;
my_total_funds = 0;
}
也可以将所有参数设置默认值
Capital::Capital(int funds = 100, const char* name = "defaultName") {
cout << "gouzao: Capital"<< endl;
m_my_total_funds = funds;
m_name = name;
}
析构函数:
析构函数没有参数(不需要参数,只是用来回收数据),声明原型为: ~Capital();// 和类名相同。
Capital capital1 = Capital(200, "wowowowo"); // 此种定义不会创建临时对象(可能有些编译器会),而是直接初始化capital1;
capital2 = Capital(200, "wowowowo");此种写法会创建一个临时对象,然后赋值给capital2,之后程序会立即自动调用析构函数。
capital1 = capital2;说明对象间可以像普通变量一样相互赋值。
默认情况下,将一个对象赋值给另一个对象,C++会将对象capital2中所有数据成员变量复制到capital1中。
const成员函数
const Capital mycapital = Capital(200, "wowowowo");// 此定义是定义mycapital为const型,说明mycapital不能被修改,是只读的。
所以调用 mycapital.show();是不合法的,因为不能保证函数show()不会修改对象成员,所以C++使用const成员函数解决此问题:
void show() const; //声明函数show() 为const,说明此函数不能修改类成员变量
void show() const { //定义函数show() 为const,说明此函数不能修改类成员变量
...
}
编程规则:如果函数不会修改类成员变量,应尽可能的声明函数为 const成员函数
注意:c中普通函数防止修改参数,是在参数列表中声明参数为const。
作用域内枚举:
enum class egg {small, medium, jumbo}
使用: rgg choice = egg:: small;