目录
二、c++面向对象知识
2.1 c++类和对象
1. c++类的成员函数
类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。
定义成员函数
- 成员函数可以定义在类定义内部;
- 使用范围解析运算符 :: 来定义。
// (1)类内定义
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(void)
{
return length * breadth * height;
}
};
// (2)类外定义,成员函数类内声明
double Box::getVolume(void)
{
return length * breadth * height;
}
2. c++类访问修饰符
(1) public
公有成员在程序中类的外部是可访问的。
(2) private
私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。
默认情况下,类的所有成员都是私有的。
#include <iostream>
using namespace std;
class Box
{
public:
double length;
void setWidth( double wid );
double getWidth( void );
private:
double width;
};
// 成员函数定义
double Box::getWidth(void)
{
return width ;
}
void Box::setWidth( double wid )
{
width = wid;
}
// 程序的主函数
int main( )
{
Box box;
// 不使用成员函数设置长度
box.length = 10.0; // OK: 因为 length 是公有的
cout << "Length of box : " << box.length <<endl;
// 不使用成员函数设置宽度
// box.width = 10.0; // Error: 因为 width 是私有的
box.setWidth(10.0); // 使用成员函数设置宽度
cout << "Width of box : " << box.getWidth() <<endl;
return 0;
}
(3) protected
protected成员变量或函数与私有成员十分相似,但protected成员在派生类(即子类)中是可访问的。
(子类可以访问protected)
#include <iostream>
using namespace std;
class Box
{
protected:
double width;
};
class SmallBox:Box // SmallBox 是派生类
{
public:
void setSmallWidth( double wid );
double getSmallWidth( void );
};
// 子类的成员函数
double SmallBox::getSmallWidth(void)
{
return width ;
}
void SmallBox::setSmallWidth( double wid )
{
width = wid;
}
// 程序的主函数
int main( )
{
SmallBox box;
// 使用子类对象调用子类成员函数设置宽度
box.setSmallWidth(5.0);
cout << "Width of box : "<< box.getSmallWidth() << endl;
return 0;
}
(4)继承中的特点
- public 继承:基类成员的访问属性在派生类中保持不变
- protected 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:protected, protected, private
- private 继承:基类 public ,protected,private 的访问属性在派生类中分别变成:private, private, private。
继承方式 | 基类的public成员 | 基类的protected成员 | 基类的private成员 | 继承引起的访问控制关系变化概括 |
---|---|---|---|---|
public继承 | 仍为public成员 | 仍为protected成员 | 不可见 | 基类的非私有成员在子类的访问属性不变 |
protected继承 | 变为protected成员 | 变为protected成员 | 不可见 | 基类的非私有成员都为子类的保护成员 |
private继承 | 变为private成员 | 变为private成员 | 不可见 | 基类中的非私有成员都称为子类的私有成员 |
2.2 c++ 继承
1. 访问控制和继承
一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数。定义一个派生类,我们使用一个类派生列表来指定基类。类派生列表以一个或多个基类命名。
#include <iostream>
using namespace std;
// 基类
class Shape
{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
// 派生类
class Rectangle: public Shape
{
public:
int getArea()
{
return (width * height);
}
void setW2(int w)
{
width = w * 2; // 派生类可以访问基类的保护对象
}
};
int main(void)
{
Rectangle Rect;
Rect.setWidth(5); // 派生类调用基类函数
Rect.setHeight(7);
// 输出对象的面积
cout << "Total area: " << Rect.getArea() << endl;
Rect.setW2(7);
cout << "Total area: " << Rect.getArea() << endl;
return 0;
}
一个派生类继承了所有的基类方法,但下列情况除外:
- 基类的构造函数、析构函数和拷贝构造函数。
- 基类的重载运算符。
- 基类的友元函数。
2. 多继承
C++ 类可以从多个类继承成员:
// 派生类
class Rectangle: public Shape, public PaintCost
{
public:
int getArea()
{
return (width * height);
}
};
2.3 c++ 重载
c++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。
当调用一个重载函数或重载运算符时,编译器通过将使用的参数类型与定义中的参数类型进行比较。
1. C++ 中的函数重载
在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。您不能仅通过返回类型的不同来重载函数。
#include <iostream>
using namespace std;
class printData
{
public:
void print(int i) {
cout << "整数为: " << i << endl;
}
void print(double f) {
cout << "浮点数为: " << f << endl;
}
void print(char c[]) {
cout << "字符串为: " << c << endl;
}
};
int main(void)
{
printData pd;
// 输出整数
pd.print(5);
// 输出浮点数
pd.print(500.263);
// 输出字符串
char c[] = "Hello C++";
pd.print(c);
return 0;
}
2. C++ 中的运算符重载
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。
// 定义为成员函数(类内)
Box operator+(const Box&);
// 定义为非成员函数(类外)
Box operator+(const Box&, const Box&);
- 类的非成员函数,每次操作需要传递两个参数
- 类的成员函数中,对象作为参数进行传递,对象的属性使用 this 运算符访问
#include <iostream>
using namespace std;
class Box
{
public:
double getArea(void)
{
return length * breadth;
}
void setLength( double len )
{
length = len;
}
void setBreadth( double bre )
{
breadth = bre;
}
// 重载 + 运算符,用于把两个 Box 对象相加
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
return box;
}
private:
double length; // 长度
double breadth; // 宽度
};
// 程序的主函数
int main( )
{
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
Box Box3; // 声明 Box3,类型为 Box
double area = 0.0; // 把体积存储在该变量中
// Box1 详述
Box1.setLength(6.0);
Box1.setBreadth(6.0);
// Box2 详述
Box2.setLength(10.0);
Box2.setBreadth(11.0);
// Box1 的体积
area = Box1.getArea();
cout << "Area of Box1 : " << area <<endl;
// Box2 的体积
area = Box2.getArea();
cout << "Area of Box2 : " << area <<endl;
// 把两个对象相加,得到 Box3
Box3 = Box1 + Box2;
// Box3 的体积
area = Box3.getArea();
cout << "Area of Box3 : " << area <<endl;
return 0;
}
- 运算符重载也可以发生函数重载
// 类的非成员函数无法访问private,需要设置为public或者通过成员函数访问
//通过全局函数运算符重载
Box operator+ (const Box& b1, const Box& b2)
{
Box temp;
temp.length = b1.length + b2.length;
temp.breadth= b1.breadth + b2.breadth;
return temp;
}
//运算符重载也可以发生函数重载
Box operator+ (const Box& b1, const int num)
{
Box temp;
temp.length = b1.length + num;
temp.breadth = b1.breadth+ num;
return temp;
}
可重载运算符/不可重载运算符
(1) 可重载的运算符
运算符 | 具体 |
---|---|
双目算术运算符 | + ,-,*,/,% (取模) |
关系运算符 | ==,!= ,< ,> ,<=,>= |
逻辑运算符 | ||,&& ,! |
单目运算符 | + (正),-(负),*(指针),&(取地址) |
自增自减运算符 | ++,– |
位运算符 | | ,& ,~,^,,<< ,>> |
赋值运算符 | =, +=, -=, *=, /= , % = , &=, |=, ^=, <<=, >>= |
空间申请与释放 | new, delete, new[ ] , delete[] |
其他运算符 | ()(函数调用),->(成员访问),,(逗号),[](下标) |
(2) 不可重载的运算符
运算符 | 解释 |
---|---|
. | 成员访问运算符 |
., -> | 成员指针访问运算符 |
:: | 域运算符 |
sizeof | 长度运算符 |
?: | 条件运算符 |
# | 预处理符号 |
2.4 c++多态
当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
C++ 多态意味着调用成员函数时,会根据调用函数对象的类型来执行不同的函数。核心理念就是通过基类访问派生类定义的函数。
1. 多态的分类
- 静态多态:函数重载,泛型编程
- 动态多态:虚函数(重写)
2. 静态多态
编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。
静态多态有两种实现方式:
- 函数重载:包括普通函数的重载和成员函数的重载
- 函数模板的使用(泛型编程)
// print()函数
void print(const char* str,int width);
void print(double i ,int width);
void print(const char* str);
// 使用print()函数时,编译器将根据所采取的用法使用有相应特征标的原型
print("abc",12);
print(2.2,55);
print("def");
3. 动态多态
虚函数
虚函数是在基类中使用关键字 virtual声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
纯虚函数
基类中不能对虚函数给出有意义的实现,但是希望在派生类中给出定义以便更好的适用对象。这个时候就会用到纯虚函数。
// 纯虚函数定义
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
// pure virtual function
virtual int area() = 0;
};
多态的实现
#include <iostream>
using namespace std;
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
// 不加virtual是静态链接
virtual int area()
{
cout << "Parent class area :" <<endl;
return 0;
}
};
class Rectangle: public Shape{
public:
Rectangle( int a=0, int b=0):Shape(a, b) { }
// 多态
int area ()
{
cout << "Rectangle class area :" <<endl;
return (width * height);
}
};
class Triangle: public Shape{
public:
Triangle( int a=0, int b=0):Shape(a, b) { }
// 多态
int area ()
{
cout << "Triangle class area :" <<endl;
return (width * height / 2);
}
};
// 程序的主函数
int main( )
{
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
// 存储矩形的地址
shape = &rec;
// 调用矩形的求面积函数area
shape->area();
// 存储三角形的地址
shape = &tri;
// 调用三角形的求面积函数area
shape->area();
/*非多态
rec.area();
tri.area();
*/
return 0;
}
形成多态的三个必备条件
- 存在继承关系
- 继承关系有同名虚函数
- 存在基类的指针或引用,通过该指针和引用调用虚函数
4. c++ 接口(抽象类)
-
C++ 接口是使用抽象类来实现的,如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用 “= 0” 来指定的.
-
设计抽象类(通常称为 ABC)的目的,是为了给其他类提供一个可以继承的适当的基类。抽象类不能被用于实例化对象,它只能作为接口使用。如果试图实例化一个抽象类的对象,会导致编译错误。
-
如果一个 ABC 的子类需要被实例化,则必须实现每个纯虚函数。如果没有在派生类中重写纯虚函数,就尝试实例化该类的对象,会导致编译错误。
class Box
{
public:
// 纯虚函数
virtual double getVolume() = 0;
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
5. 重载和重写区别
-
范围区别:重写和被重写的函数在不同的类中,重载和被重载的函数在同一类(作用域)中。
-
参数区别:重写与被重写的函数参数列表一定相同,重载和被重载的函数参数列表一定不同。
-
virtual的区别:重写的基类函数必须要有virtual修饰,重载函数和被重载函数可以被virtual修饰,也可以没有