类
类和结构体
结构体与类非常相似,都是自定义的数据类型。但不同的是,结构体中所有成员的控制访问权限都是public,而类中数据成员默认为private,也就是说,类对数据成员实现了封装,而且类还能够参与继承和多态,所以说类是面向对象程序设计的基础。
类成员的访问权限
关键字 | 权限 |
---|---|
private | 只有类中的函数和友元能访问 |
public | 所有对象都可以访问 |
private | 只有派生类可以访问 |
类的结构
class Person{
private:
string name;
int age;
public:
Person(string n,int a): name(n),age(a){};
~Person(){};
const string &getName() const {
return name;
}
void setName(const string &name) {
Person::name = name;
}
int getAge() const {
return age;
}
void setAge(int age) {
Person::age = age;
}
};
数据域
数据域中存储类中的数据,与结构体类似,可以存储各种基础类型、指针类型和自定义类型,但是,类也不能嵌套定义。
按照面向对象程序设计的要求,数据域中的成员都应该为private(不这样操作也不错)
函数域
构造函数
构造函数是每个类必须有的成员,在使用类生成对象时,系统会自动调用类的构造函数。如果没有自定义的构造函数,系统就会自动生成一个空的构造函数。
构造函数的控制访问权限必须是public,而且函数名与类名相同,不需要返回值。
当需要用构造函数为数据赋值时,可以像上面那样使用 : 运算符,也可以在函数中赋值。
Person(string n,int a)
{
name = n;
age = a;
}
this指针
每个对象都会有一个this指针,这个可以看成是C++系统自动完成的,我们只要使用即可。
this表示当前对象(有点难表达),以上面的例子,我们知道,在函数传参时最好给形参取上有意义的名字,那么上面的代码就要改成:
Person(string name,int age)
{
name = name;
age = age;
}
这段代码显然是无法执行的,因为编译器没办法分清等号前的变量和等号后的变量有什么区别。
所以,这时就要使用this指针,表示等号前的变量是这个正在操作的对象的成员
Person(string name,int age)
{
this->name = name;
this->age = age;
}
复制构造函数
复制构造函数会在对象复制时,比如向函数里传参,函数返回值时用到。系统不会自动生成,必须自己定义。
Person(const Person &person){
name = person.name;
age = person.age;
}
析构函数
析构函数也是每个类都有的,在类的对象消亡时,系统会调用其析构函数。
析构函数不需要参数,在函数名前加~,函数名与类名相同。
友元
我们知道,根据面向对象的程序设计思想,所有数据成员都应该被封装(可以理解成设为private),当我们想修改数据成员是应该加上getter和setter方法。
const string &getName() const {
return name;
}
void setName(const string &name) {
Person::name = name;
}
int getAge() const {
return age;
}
void setAge(int age) {
Person::age = age;
}
这里稍微解释一下,getter方法名后的const表示得到的数不能再做修改,这对于保持类的封装性至关重要。
但在有些特殊情况下,我们需要更高效的访问类的数据成员,这个时候就要用到友元。
友元函数
友元函数是可以直接访问类private和protected成员的非成员函数
友元函数不是类的成员函数。
可以在以上Person类中加上一行代码(放在private区和public区都不影响)
friend void print(Person person);
然后在类外定义函数
void print(Person person){
cout<< person.name<<person.age;
}//友元函数可以直接访问Person类中的成员
一个类可以有多个友元函数,一个函数也可以是多个类的友元。
友元类
汽车需要快速地访问发动机的状态,所以把汽车类设置为发动机的友元,即汽车类的成员函数可以直接访问发动机类的private和protected数据成员。
class Engine
{
private:
int maxSpeed;
string name;
public:
friend class Car;//定义友元类,即Car的所有方法都是Engine的友元方法
Engine(int maxSpeed, const string &name) : maxSpeed(maxSpeed), name(name) {}
int getMaxSpeed() const {
return maxSpeed;
}
void setMaxSpeed(int maxSpeed) {
Engine::maxSpeed = maxSpeed;
}
const string &getName() const {
return name;
}
void setName(const string &name) {
Engine::name = name;
}
};
class Car{
private:
double lenth;
Engine engine;
public:
Car(double lenth, const Engine &engine) : lenth(lenth), engine(engine) {}
int getMaxSpeed(){
return engine.maxSpeed;//可以直接访问Engine的private成员
}
};
这里要特别注意,友元类是单向的,不可传递的,a是b的友元,但b不是a的友元;a是b的友元,b是c的友元,但a不是c的友元。
组合
指在一个类中包含有其他类的对象
Class B{
int x;
public:
B(int a) x(a){};
~B(){};
}
Class A{
private :
B b;
int k;
public :
A(int x,int y):k(x),b(x){}
~A(){}
}
- 内部类的构造函数先于A的构造函数被调用
- 内部类的析构函数后于A的析构函数被调用
- 初始化顺序按A中声明顺序
运算符重载
我们现在有一个直线类
class Line{
private:
double lenth;
public:
Line(double lenth) : lenth(lenth) {}
Line(const Line& line){
lenth = line.lenth;
}
double getLenth() const {
return lenth;
}
void setLenth(double lenth) {
Line::lenth = lenth;
}
};
我们现在想让两条直线拼接起来,即让两条直线长度相加
Line l1(5),l2(2);
如果不使用运算符重载,我们可以写一个成员函数
public void add(const Line &line)
{
lenth = lenth + line.lenth;
}
但这似乎有点麻烦,按照我们的习惯,如果能使用 l1 +=l2这样的形式应该更好。但+=运算符是针对基本数据类型设计的,想让它适用于Line类,我们必须改变其功能(重载运算符)。
重载算数运算符
void operator+=(const Line &line){
lenth += line.lenth;
}
这里重载了+=运算符,那为什么不直接重载+运算符呢?
因为+=运算符已知是在第一个数的基础上进行运算,而+运算符需要两个操作数,没有办法判断是在哪个数的基础上进行运算,所以重载+运算符就必须通过友元函数重载一个全局方法(一般考试的话应该只要求会重载+=即可)
friend Line operator + (Line &l1,Line &l2){
Line line(0);
line.lenth = l1.lenth+l2.lenth;
return line;
}
重载逻辑运算符
bool operator==(const Line &rhs) const {
return lenth == rhs.lenth;
}
bool operator!=(const Line &rhs) const {
return !(rhs == *this);
}
bool operator<(const Line &rhs) const {
return lenth < rhs.lenth;
}
bool operator>(const Line &rhs) const {
return rhs < *this;
}
bool operator<=(const Line &rhs) const {
return !(rhs < *this);
}
bool operator>=(const Line &rhs) const {
return !(*this < rhs);
}
重载流运算符
重载流运算符是要通过友元重载一个全局函数(这个格式很固定,记住即可)
friend ostream &operator<<(ostream &os, const Line &line) {
os << "lenth: " << line.lenth;
return os;
}