六、类和对象
- 概念
- 四大特性
- 类和对象
- 类(Class)
- 对象(Object)
- 访问修饰符
- 类的创建和对象的初始化
- 创建类
- 初始化对象
- `class`与`struct` 的区别
- 类的声明和实现分离
- `string`
- 包含头文件
- 声明和初始化字符串
- 访问字符串中的字符
- 修改字符串
- 字符串连接
- 字符串长度
- 字符串比较
- 查找子字符串
- 字符串截取
- 字符串转换
- 常用函数
- 构造函数
- 访问函数
- 容量函数
- 修改函数
- 搜索和查找函数
概念
面向对象(Object Oriented)是一种软件开发方法和程序设计范式,它强调从现实世界的客观存在出发,以对象作为系统的基本构成单位来构造软件系统。
- 面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。
- 它将现实世界中的事物抽象为对象,将事物之间的关系抽象为类、继承等概念。
- 面向对象是相对于面向过程来讲的,它更贴近事物的自然运行模式,将相关的数据和方法组织为一个整体来看待。
- 面向过程:就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
- 面向对象:是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为。
四大特性
面向对象的四大特性(也称为OOP的四大支柱)包括:抽象(Abstraction)、封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism)。
-
抽象(Abstraction):
抽象是指将现实世界中的某一类事物共有的特征抽象出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。通过抽象,我们可以忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。 -
封装(Encapsulation):
封装是把对象的属性和操作结合成一个独立的系统单位,并尽可能隐藏对象的内部细节(属性和方法的实现细节)。封装的主要目的是增强安全性和简化编程,使用者无需了解具体的实现细节,而只是通过外部接口、以特定的访问权限来使用类的成员。 -
继承(Inheritance):
继承是面向对象编程(OOP)实现代码重用的一个重要手段。如果一个类A继承自另一个类B,就把类B叫做类A的父类(超类、基类),类A叫做类B的子类(派生类)。子类可以直接访问父类的非私有的属性和方法,这就是继承的基本思想。通过继承,我们可以基于已存在的类来定义新的类,这样可以大大提高代码的重用性。 -
多态(Polymorphism):
多态是指不同的对象对同一消息做出不同的响应。多态包括参数多态和包含多态。多态性允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。
这四大特性共同构成了面向对象编程的基础,使得代码更加模块化、可重用和可扩展。在软件开发过程中,合理地使用这些特性可以提高开发效率和代码质量。
类和对象
在C++中,类和对象是面向对象编程(OOP)的核心概念。类(Class)是一个用户定义的数据类型,它定义了一个对象的属性和方法(在C++中通常称为成员函数)。对象(Object)是类的实例,它是根据类创建的具体存在。
类(Class)
类定义了对象的蓝图或模板,它指定了对象应有的属性和可以执行的操作。在C++中,类使用class
关键字来定义。
对象(Object)
对象是类的实例。你可以使用类来创建对象,并通过这些对象来访问类的成员变量和成员函数。
访问修饰符
在C++中,类的成员(包括成员变量和成员函数)可以有三种访问修饰符:public、protected和private。默认情况下,如果不指定访问修饰符,则成员的访问级别是private。
- public:成员可以从任何地方被访问。
- protected:成员可以被其派生类(子类)和同一个包(在C++中,这通常指的是同一个类)中的其他成员访问。
- private:成员只能被同一个类中的其他成员访问。
类的创建和对象的初始化
在C++中,类的创建是通过class
关键字来完成的,而对象的初始化则涉及到类的构造函数。下面是一个详细的例子,说明如何创建类以及如何使用构造函数来初始化对象。
创建类
首先,你需要定义一个类。类定义了对象的属性和方法。以下是一个简单的Rectangle
类的例子:
class Rectangle {
private: // 通常,成员变量应声明为私有,以保持封装性double width;double height;public: // 公有成员函数可以被类的对象访问// 默认构造函数Rectangle() : width(0.0), height(0.0) {}// 带参数的构造函数Rectangle(double w, double h) : width(w), height(h) {}// 计算面积的方法double area() const { return width * height; }// 计算周长的方法double perimeter() const { return 2 * (width + height); }// 可能的setter和getter方法(如果需要的话)void setWidth(double w) { width = w; }void setHeight(double h) { height = h; }double getWidth() const { return width; }double getHeight() const { return height; }
};
在这个例子中,Rectangle
类有两个私有成员变量width
和height
,以及几个公有成员函数,包括两个构造函数、计算面积和周长的方法,以及可能的setter和getter方法。
初始化对象
要创建一个Rectangle
类的对象,你需要使用类的构造函数。构造函数是一种特殊的成员函数,它在创建类的对象时自动调用。以下是如何使用上面的Rectangle
类来创建和初始化对象的例子:
int main() {// 使用默认构造函数创建一个Rectangle对象,其width和height都初始化为0Rectangle rect1;// 使用带参数的构造函数创建一个Rectangle对象,并指定width和height的值Rectangle rect2(10.0, 5.0);// 访问对象的公有成员函数std::cout << "Area of rect1: " << rect1.area() << std::endl; // 输出0,因为width和height都是0std::cout << "Area of rect2: " << rect2.area() << std::endl; // 输出50// 可以通过setter方法修改对象的属性值rect2.setWidth(20.0);std::cout << "Area of rect2 after modifying width: " << rect2.area() << std::endl; // 输出100return 0;
}
在这个例子中,我们首先使用默认构造函数创建了一个Rectangle
对象rect1
,它的width
和height
都被初始化为0。然后,我们使用带参数的构造函数创建了一个Rectangle
对象rect2
,并指定了width
为10.0,height
为5.0。接着,我们调用了对象的area()
方法来计算并打印面积。最后,我们使用setWidth()
方法修改了rect2
的width
属性值,并再次计算并打印了面积。
class
与struct
的区别
class
和struct
在编程中都是用于定义用户自定义数据类型的关键字,但它们之间存在一些显著的区别。
-
类型与存储:
class
是引用类型,它在堆中分配空间,栈中保存的是指向该对象的引用。struct
是值类型,它在栈中分配空间,并且直接存储数据。
-
继承与派生:
class
支持继承,允许一个类继承自另一个类,从而继承其属性和方法。struct
不支持继承,它不能被其他struct
或class
继承,也不能从其他类型继承。
-
成员访问权限:
class
的成员可以是私有的(private)、保护的(protected)或公有的(public)。struct
的成员默认为公有(public),但也可以明确指定为私有或保护。
-
构造函数与初始化:
class
的构造器需要显式定义,如果不手动实现构造函数,编译器会报错(除非所有的成员变量都是可选的或有默认值)。struct
的构造器是隐式的,编译器会自动生成一个无参数的构造函数,即使成员变量没有初始化,编译器也不会报错。
-
内存管理:
- 由于
class
是引用类型,所以在赋值或作为参数传递时,传递的是引用而非实际对象,这可能导致意外的数据共享和修改。 struct
作为值类型,在赋值或作为参数传递时,传递的是实际数据的副本,因此修改一个副本不会影响原始数据。
- 由于
-
与接口的关系:
class
和struct
都可以实现接口,但如果没有实现接口的方法,class
会报错,而struct
则不会。
-
可空性:
- 在C#等语言中,
class
类型的变量可以被设置为null
,表示没有引用任何对象。 struct
由于是值类型,不能被设置为null
。
- 在C#等语言中,
-
性能考虑:
- 由于
struct
直接在栈上分配内存,并且在传递时复制整个数据,因此在处理大量小数据或需要频繁复制数据时,struct
可能具有更好的性能。 class
由于涉及到堆内存的分配和引用传递,可能在某些情况下导致性能下降。
- 由于
-
设计哲学:
class
通常用于表示具有复杂行为的数据结构,如对象、集合等。struct
通常用于表示简单的数据结构,如点、向量、颜色等,这些数据结构主要关注数据的表示和存储,而不涉及复杂的行为。
综上所述,class
和struct
在类型、存储、继承、成员访问权限、构造函数、内存管理、与接口的关系、可空性、性能和设计哲学等方面存在显著的区别。在选择使用class
还是struct
时,应根据具体的需求和场景进行权衡。
类的声明和实现分离
在C++中,类的声明(也称为类的定义)和类的实现(也称为成员函数的定义)通常被分离开来。这是为了保持代码的组织和清晰性,使得头文件(.h
或 .hpp
)只包含类的声明和相关的类型定义,而源文件(.cpp
)则包含类的实现。
以下是一个简单的示例,展示如何将类的声明和实现分离开来:
头文件(例如:MyClass.h
):
// MyClass.h
#ifndef MYCLASS_H // 预处理指令,防止头文件被重复包含
#define MYCLASS_Hclass MyClass {
public:MyClass(); // 构造函数声明~MyClass(); // 析构函数声明// 其他成员函数声明void doSomething();// 如果有数据成员,也可以在这里声明// 但是通常推荐将数据成员设为私有(private)
private:int someData; // 示例数据成员
};#endif // MYCLASS_H
源文件(例如:MyClass.cpp
):
// MyClass.cpp
#include "MyClass.h" // 包含头文件以访问类的声明
#include <iostream> // 如果需要使用iostream库,也要包含它// 构造函数的实现
MyClass::MyClass() {someData = 0; // 初始化数据成员std::cout << "MyClass constructor called." << std::endl;
}// 析构函数的实现
MyClass::~MyClass() {std::cout << "MyClass destructor called." << std::endl;
}// 其他成员函数的实现
void MyClass::doSomething() {std::cout << "Doing something with MyClass." << std::endl;// 可以在这里对数据成员进行操作someData++;
}
在上面的示例中,MyClass.h
是头文件,它包含了 MyClass
的声明。而 MyClass.cpp
是源文件,它包含了 MyClass
的实现(即成员函数的定义)。通过包含 MyClass.h
,MyClass.cpp
可以访问 MyClass
的声明,并为其成员函数提供实现。这种分离使得代码更加模块化,并且可以在多个源文件中重用头文件中的类声明。
string
在C++中,string
是一个非常重要的数据类型,用于存储和操作字符序列(即文本)。string
类型是标准库 <string>
中的一部分,它提供了许多有用的函数来创建、修改和查询字符串。
以下是一些使用 string
类型的基本示例:
包含头文件
首先,你需要包含 <string>
头文件来使用 string
类型。
#include <string>
声明和初始化字符串
std::string str1; // 声明一个空字符串
std::string str2 = "Hello, World!"; // 声明并初始化一个字符串
std::string str3(str2); // 使用另一个字符串初始化
std::string str4(5, 'a'); // 声明并初始化一个包含5个'a'的字符串
访问字符串中的字符
你可以使用下标操作符 []
或 at()
函数来访问字符串中的字符。
char ch = str2[0]; // 获取第一个字符
char ch2 = str2.at(0); // 同样获取第一个字符,但会进行范围检查
修改字符串
你可以通过下标操作符或 assign()
函数来修改字符串中的字符。
str2[0] = 'h'; // 修改第一个字符为小写'h'
str2.assign("new string"); // 分配新的字符串内容
字符串连接
你可以使用 +
操作符或 append()
函数来连接字符串。
std::string str5 = str2 + " and universe!"; // 连接两个字符串
str2.append(" and universe!"); // 同样连接字符串
字符串长度
你可以使用 size()
或 length()
函数来获取字符串的长度。
std::size_t len = str2.size(); // 获取字符串长度
字符串比较
你可以使用 ==
、<
、>
等操作符来比较字符串。
if (str2 == "Hello, World!") {// ...
}
查找子字符串
你可以使用 find()
函数来查找子字符串在字符串中的位置。
std::size_t pos = str2.find("World"); // 查找"World"在str2中的位置
字符串截取
你可以使用 substr()
函数来截取字符串的一部分。
std::string sub = str2.substr(7, 5); // 从索引7开始截取5个字符
字符串转换
你可以使用其他标准库函数(如 std::stoi
、std::to_string
等)来在字符串和其他数据类型之间进行转换。
常用函数
构造函数
string()
: 默认构造函数,创建一个空字符串。string(const string& str)
: 拷贝构造函数,用另一个字符串初始化。string(const char* s)
: 用C风格的字符串初始化。string(size_type n, char c)
: 创建一个包含n
个c
字符的字符串。string(const char* s, size_type n)
: 用C风格的字符串的前n
个字符初始化。string(Initializer_list<char> il)
: 用初始化列表初始化。
访问函数
at(size_type pos) const
: 返回位置pos
的字符(进行边界检查)。operator[](size_type pos) const
: 返回位置pos
的字符(不进行边界检查)。front() const
: 返回第一个字符。back() const
: 返回最后一个字符。data() const
: 返回指向字符串内部数组的指针(C风格字符串)。c_str() const
: 返回一个以空字符终止的字符数组(C风格字符串)。
容量函数
size() const
: 返回字符串中的字符数。length() const
: 与size()
相同,返回字符串中的字符数。max_size() const
: 返回字符串可能包含的最大字符数。capacity() const
: 返回在不重新分配内存的情况下可以容纳的字符数。empty() const
: 如果字符串为空,则返回true
。reserve(size_type res_arg = 0)
: 请求改变容量。shrink_to_fit()
: 请求移除未使用的容量。
修改函数
assign(const string& str)
: 用另一个字符串替换内容。assign(const string& str, size_type pos, size_type n)
: 用另一个字符串的子串替换内容。assign(const char* s)
: 用C风格的字符串替换内容。assign(const char* s, size_type n)
: 用C风格的字符串的前n
个字符替换内容。assign(size_type n, char c)
: 用n
个c
字符替换内容。append(const string& str)
: 在字符串末尾添加另一个字符串。append(const char* s)
: 在字符串末尾添加C风格的字符串。append(const char* s, size_type n)
: 在字符串末尾添加C风格的字符串的前n
个字符。append(size_type n, char c)
: 在字符串末尾添加n
个c
字符。push_back(char c)
: 在字符串末尾添加一个字符。pop_back()
: 移除字符串末尾的字符。clear()
: 移除所有字符。replace(size_type pos, size_type n, const string& str)
: 替换从位置pos
开始的n
个字符为另一个字符串。replace(size_type pos, size_type n, const char* s)
: 替换从位置pos
开始的n
个字符为C风格的字符串。replace(size_type pos, size_type n, size_type count, char c)
: 替换从位置pos
开始的n
个字符为count
个c
字符。erase(size_type pos = 0, size_type n = npos)
: 移除从位置pos
开始的n
个字符。insert(size_type pos, const string& str)
: 在位置pos
插入另一个字符串。insert(size_type pos, const char* s)
: 在位置pos
插入C风格的字符串。insert(size_type pos, size_type count, char c)
: 在位置pos
插入count
个c
字符。resize(size_type n, char c = charT())
: 改变字符串的大小,如果新大小大于当前大小,则用c
填充额外的空间。
搜索和查找函数
find(const string& str, size_type pos = 0) const
: 从位置pos
开始查找子串str
。find(const char* s, size_type pos = 0) const
: 从位置pos
开始查找C风格的子串s
。
这只是 string
类型功能的一小部分。为了充分利用 string
类型,建议查阅C++标准库文档或相关教程以获取更多信息。