C++基础
提示
对有一定的C语言基础,想学C++的同学们的,可以参考这篇文章。
基础
后续有必要的话,会补充一些基础内容。
类和对象
类和对象是C++面向对象编程(OOP)中的核心概念。以下是关于类和对象的详细解释,以列表的形式展示:
类(Class)
- 定义:类是对具有相同属性和行为的对象的抽象描述。它定义了对象的数据结构和行为。
- 组成:
- 属性(成员变量):描述对象的状态或特征的数据成员。
- 方法(成员函数):描述对象可以执行的操作或行为的函数成员。
- 访问修饰符:
- public:成员在类的外部也可以访问。
- private:成员只能在类的内部访问。
- protected:成员在类的内部和派生类中可以访问。
- 构造函数与析构函数:
- 构造函数:用于初始化对象的成员变量。
- 析构函数:用于清理对象销毁前的资源。
- 继承:类可以从其他类继承,从而重用和扩展代码。
- 友元:允许类或函数访问另一个类的私有和保护成员。
对象(Object)
- 定义:对象是类的实例,它拥有类定义的所有属性和方法。
- 创建:使用类的构造函数来创建对象。
- 内存分配:每个对象在内存中都有唯一的地址,占用一定的存储空间。
- 访问成员:通过对象可以访问其类的公有成员变量和成员函数。
- 生命周期:对象的生命周期从它被创建开始,到它被销毁结束。
示例代码
#include<iostream>
using namespace std;
class Rectangle
{
private:
double length, width;
public:
Rectangle(Rectangle &Rect); // 拷贝构造函数
Rectangle(double a, double b); // 构造函数
void area();
~Rectangle(); // 析构函数
};
Rectangle::Rectangle(Rectangle &Rect)
{
length = Rect.length + 10;
width = Rect.width + 10;
}
Rectangle::Rectangle(double a = 1, double b = 1)
{
length = a;
width = b;
}
Rectangle::~Rectangle()
{
cout << "Quit Rectangle!" << endl;
}
void Rectangle::area()
{
cout << "The Area is " << length *width << endl;
}
int main()
{
Rectangle rect(5, 6);
rect.area();
// 方式1
// Rectangle rect2(rect);
// 方式2
Rectangle rect2 = rect;
rect2.area();
return 0;
}
继承
在C++中,继承(Inheritance)是面向对象编程的四大基本特性之一,它允许一个类(派生类或子类)继承另一个类(基类或父类)的属性和方法。通过继承,我们可以创建更加层次化的类结构,实现代码的重用和扩展。
以下是关于C++中继承的详细解释:
继承类型
C++支持三种类型的继承:
公有继承(Public Inheritance):基类中的公有成员在派生类中仍然是公有的,保护成员在派生类中保持为保护成员,私有成员在派生类中是不可访问的。
保护继承(Protected Inheritance):基类中的公有成员和保护成员在派生类中都将变为保护成员,私有成员仍然是不可访问的。
私有继承(Private Inheritance):基类中的公有成员和保护成员在派生类中都将变为私有成员,私有成员在派生类中是不可访问的。
继承语法
class Base { // 基类
public:
void baseFunction() { /* 基类成员函数 */ }
};
class Derived : public Base { // 公有继承派生类
public:
void derivedFunction() { /* 派生类成员函数 */ }
};
在上面的代码中,Derived
类公有继承自 Base
类。因此,Derived
类的对象可以调用 baseFunction()
。
访问控制
继承会影响基类成员的访问级别,这取决于继承的类型。例如,在公有继承中,基类的公有成员在派生类中仍然是公有的。但在私有继承中,基类的公有成员在派生类中会变成私有的。
重写(Override)和重载(Overload)
重写(Override):在派生类中定义一个与基类同名的成员函数,用于覆盖基类的实现。这通常发生在基类函数需要被派生类以不同方式实现时。
重载(Overload):在同一个类中定义多个同名但参数列表不同的函数。这与继承无直接关联,但经常在面向对象编程中使用。
构造函数和析构函数
当创建派生类的对象时,基类的构造函数会被自动调用。如果需要在派生类的构造函数中执行特定的初始化操作,并同时调用基类的构造函数,可以在派生类的构造函数的初始化列表中显式地调用基类的构造函数。同样地,当派生类的对象被销毁时,派生类的析构函数会首先被调用,然后是基类的析构函数。
示例
#include <iostream>
using namespace std;
class Base
{
public:
Base()
{
cout << "Base constructor called" << endl;
}
~Base()
{
cout << "Base destructor called" << endl;
}
virtual void show()
{
cout << "Base class show()" << endl;
}
};
class Derived : public Base
{
public:
Derived() : Base()
{
cout << "Derived constructor called" << endl;
}
~Derived()
{
cout << "Derived destructor called" << endl;
}
void show() override
{
cout << "Derived class show()" << endl;
}
};
int main()
{
Derived d; // 调用构造函数:Base -> Derived
d.show(); // 调用Derived类的show()函数
return 0; // 调用析构函数:Derived -> Base
}
在上面的代码中,Derived
类公有继承自 Base
类,并重写了 show()
方法。在 main()
函数中,我们创建了一个 Derived
类的对象 d
,并调用了它的 show()
方法。注意构造函数和析构函数的调用顺序,以及虚函数 show()
的重写。输出如下:
Base constructor called
Derived constructor called
Derived class show()
Derived destructor called
Base destructor called
多态
多态(Polymorphism)是面向对象编程(OOP)中的一个核心概念,它指的是允许一个接口(引用变量)引用多种实际类型,并且程序能够根据不同的实际类型做出相应的处理。简单来说,多态就是多种形态,意味着不同的对象对同一消息(方法调用)可以做出不同的响应。
多态的实现主要依赖于继承、接口和重写(Override)机制。当子类继承自父类并重写父类的方法时,通过父类的引用可以调用子类的实现,从而实现多态。此外,接口的多态实现允许不同的对象对相同的消息作出不同的响应,而无需关心具体的实现细节。
多态的作用主要体现在以下几个方面:
- 提高了代码的维护性:由于多态允许将不同的子类对象当作父类对象处理,当需要添加新的子类时,只需要实现相应的接口或继承父类并重写方法,而无需修改现有代码。
- 提高了代码的扩展性:多态使得程序更加灵活,能够根据具体情况动态地确定对象的行为。通过添加新的子类并实现相应的接口或方法,可以轻松地扩展程序的功能。
- 实现代码复用:通过继承和多态,子类可以共享父类的代码和方法,减少了代码的重复编写,提高了代码复用性。
多态的应用场景非常广泛,例如在图形用户界面(GUI)编程中,不同的按钮对象可以共享相同的点击事件处理方法,但根据按钮的类型或状态执行不同的操作。这种情况下,多态能够使得代码更加简洁、灵活和易于维护。
示例
以下是一个简单的多态示例,使用C++编程语言来展示多态的概念:
首先,我们定义一个基类Shape
,它有一个虚函数draw()
:
#include <iostream>
#include <string>
class Shape {
public:
virtual void draw() const {
std::cout << "Drawing a generic shape" << std::endl;
}
virtual ~Shape() {} // 虚析构函数确保子类被正确销毁
};
接着,我们定义两个继承自Shape
的子类:Circle
和Rectangle
,并重写draw()
方法:
class Circle : public Shape {
public:
void draw() const override {
std::cout << "Drawing a circle" << std::endl;
}
};
class Rectangle : public Shape {
public:
void draw() const override {
std::cout << "Drawing a rectangle" << std::endl;
}
};
在main()
函数中,我们使用一个指向Shape
的指针数组来存储不同形状的对象,并调用它们的draw()
方法:
int main() {
// 创建形状对象
Shape* shapes[2];
shapes[0] = new Circle();
shapes[1] = new Rectangle();
// 使用多态调用 draw 方法
for (int i = 0; i < 2; ++i) {
shapes[i]->draw();
}
// 释放动态分配的内存
for (int i = 0; i < 2; ++i) {
delete shapes[i];
}
return 0;
}
在这个例子中,shapes
数组中的元素是指向Shape
类的指针,但实际上它们指向的是Circle
和Rectangle
对象。当我们通过shapes
数组中的指针调用draw()
方法时,会根据实际指向的对象类型调用相应的draw()
实现。这就是多态的体现。
运行上述代码,输出将会是:
Drawing a circle
Drawing a rectangle
这个例子展示了多态的一个关键特性:即使引用(这里是指针)的类型是基类,实际调用的方法却是根据对象的实际类型(这里是Circle
或Rectangle
)来确定的。这种机制使得代码更加灵活和可扩展,因为我们可以轻松地添加新的形状类,而无需修改现有代码。
运算符重载
运算符重载(Operator Overloading)是C++中一种特殊的语法功能,它允许用户为自定义类型重新定义或重载大部分内置运算符的含义。这样,用户自定义类型(如类或结构体)的对象就可以使用与内置类型(如int或double)相似的语法进行运算。
运算符重载的几种形式
- 成员函数重载:运算符以成员函数的形式重载,通常用于只有一个操作数与类对象相关的情况。
- 非成员函数重载:运算符以非成员函数的形式重载,通常用于有两个操作数都与类对象相关的情况。在这种情况下,通常需要至少一个操作数的类型是类类型,另一个操作数可以是任意类型。
- 友元函数重载:运算符以类的友元函数形式重载,这样它就可以访问类的私有和保护成员。
运算符重载的规则和限制
- 不是所有运算符都可以被重载。例如,
.
、.*
、::
、?:
、sizeof
和typeid
等运算符不能被重载。 - 重载的运算符不能改变其原有的语义,例如,重载
+
运算符不能让它执行减法操作。 - 重载的运算符必须至少有一个操作数是用户自定义类型。
- 重载的运算符不能创建新的运算符,只能重载已存在的运算符。
- 重载的运算符不能有新的优先级和结合性。
示例
#include<iostream>
#include<math.h>
using namespace std;
class Complex
{
private:
double real, imag;
public:
Complex(double real = 0, double imag = 0)
{
this->real = real;
this->imag = imag;
}
Complex operator + (Complex complex);
Complex operator - (Complex complex);
Complex operator * (Complex complex);
Complex operator / (Complex complex);
Complex operator = (Complex complex);
friend istream &operator >>(istream &input, Complex &c);
friend ostream &operator <<(ostream &output, Complex &c);
void show()
{
cout << "complex: " << real << "+" << imag << "i" << endl;
}
} ;
Complex Complex::operator + (Complex complex)
{
return Complex(real + complex.real, imag + complex.imag);
}
Complex Complex::operator - (Complex complex)
{
return Complex(real - complex.real, imag - complex.imag);
}
Complex Complex::operator * (Complex complex)
{
Complex c;
c.real = real * complex.real - imag * complex.imag;
c.imag = real * complex.imag + imag * complex.real;
return c;
}
Complex Complex::operator / (Complex complex)
{
Complex c;
c.real = (real * complex.real + imag * complex.imag) / (pow(complex.real, 2) + pow(complex.imag, 2));
c.imag = (complex.real * imag - real * complex.imag) / (pow(complex.real, 2) + pow(complex.imag, 2));
return c;
}
Complex Complex::operator = (Complex complex)
{
real = complex.real;
imag = complex.imag;
return complex;
}
istream &operator >>(istream &input, Complex &c)
{
input >> c.real >> c.imag;
return input;
}
ostream &operator <<(ostream &output, Complex &c)
{
output << "(" << c.real << "+" << c.imag << "i)" << endl;
return output;
}
int main()
{
Complex c1(1, 2), c2(3, 4), c3;
cout << "c1_";
c1.show();
cout << "c2_";
c2.show();
c3 = c1 + c2;
cout << "c1+c2__";
c3.show();
c3 = c1 - c2;
cout << "c1-c2__";
c3.show();
c3 = c1 * c2;
cout << "c1*c2__";
c3.show();
c3 = c1 / c2;
cout << "c1/c2__";
c3.show();
cout << c3;
cout << "Please enter a complex number with the real part and the imaginary part separated by a space:" << endl;
cin >> c3;
cout << c3;
return 0;
}