C++ Primer Plus 第6版 读书笔记(10) 第十章 类与对象
第十章 类与对象
在面向对象编程中,类和对象是两个重要的概念。
类(Class)是一种用户自定义的数据类型,用于封装数据和操作。它是对象的模板或蓝图,描述了对象的属性(成员变量)和行为(成员函数)。我们可以通过定义类来创建多个具有相似特性和行为的对象。
对象(Object)是类的实例化,是内存中的一个具体存在。每个对象都有自己的属性和行为,可以独立地执行操作。通过创建对象,我们可以使用类中定义的成员变量和成员函数。
下面是一个简单的示例,演示了如何定义一个类和创建类的对象:
#include <iostream>// 定义一个名为Person的类
class Person {
public:// 成员变量std::string name;int age;// 成员函数void display() {std::cout << "Name: " << name << ", Age: " << age << std::endl;}
};int main() {// 创建Person类的对象Person person1;person1.name = "Alice";person1.age = 25;person1.display();// 创建另一个Person类的对象Person person2;person2.name = "Bob";person2.age = 30;person2.display();return 0;
}
 
在上面的示例中,我们定义了一个名为 Person 的类。这个类有两个成员变量 name 和 age,以及一个成员函数 display()。然后,在 main() 函数中,我们分别创建了两个 Person 类的对象 person1 和 person2,并给它们的成员变量赋值。最后,我们通过调用对象的成员函数 display() 来展示对象的属性。
输出结果为:
Name: Alice, Age: 25
Name: Bob, Age: 30
 
通过类和对象,我们可以更加方便地组织和管理数据,并定义与数据相关的操作,实现面向对象编程的特性。
当一个类从另一个类派生出来时,我们称之为继承(Inheritance)。通过继承,子类可以继承父类的属性和行为,并且可以在此基础上添加新的属性和行为,从而实现代码的重用性和扩展性。
有两种常见的继承关系:
- 单继承(Single Inheritance):一个子类只能继承一个父类。
 - 多继承(Multiple Inheritance):一个子类可以同时继承多个父类。
 
下面是一个示例,演示了单继承的情况:
#include <iostream>// 定义一个基类 Person
class Person {
public:std::string name;int age;void display() {std::cout << "Name: " << name << ", Age: " << age << std::endl;}
};// 定义一个派生类 Student,继承自 Person
class Student : public Person {
public:int studentId;void displayStudentId() {std::cout << "Student ID: " << studentId << std::endl;}
};int main() {// 创建 Student 类的对象Student student;student.name = "Alice";student.age = 20;student.studentId = 12345;student.display();student.displayStudentId();return 0;
}
 
在上面的示例中,我们定义了一个基类 Person 和一个派生类 Student。Student 类使用关键字 public 继承了 Person 类,这表示 Student 类可以访问 Person 类中的 public 成员。
在 main() 函数中,我们创建了一个 Student 对象 student,并给它的成员变量赋值。我们可以看到,Student 对象不仅继承了 Person 类的属性和行为,还有自己独有的属性 studentId 和行为 displayStudentId()。
输出结果为:
Name: Alice, Age: 20
Student ID: 12345
 
通过继承,我们可以建立类之间的层次关系,使代码更加模块化和易于维护。子类可以重用父类的代码,并且可以根据需要进行扩展或修改。
// stack.cpp -- Stack member functions
#include "stack.h"
Stack::Stack()    // create an empty stack
{top = 0;
}bool Stack::isempty() const
{return top == 0;
}bool Stack::isfull() const
{return top == MAX;
}bool Stack::push(const Item & item) 
{if (top < MAX){items[top++] = item;return true;}elsereturn false;
}bool Stack::pop(Item & item)
{if (top > 0){item = items[--top];return true;}elsereturn false; 
}
 
这段代码是一个简单的栈(Stack)类的实现,包括了栈的成员函数的定义。
在这段代码中,Stack 类具有以下成员函数:
- 构造函数 
Stack():用于创建一个空栈,将栈顶指针top初始化为 0。 - 成员函数 
isempty():判断栈是否为空,如果栈顶指针top等于 0,则返回true,否则返回false。 - 成员函数 
isfull():判断栈是否已满,如果栈顶指针top等于MAX(即栈的最大容量),则返回true,否则返回false。 - 成员函数 
push(const Item & item):将元素item入栈,如果栈未满,将item存入栈顶指针top对应的位置,并将top加一。如果栈已满,则返回false。 - 成员函数 
pop(Item & item):将栈顶元素出栈,并将其存入item中,如果栈非空,将栈顶指针减一,然后返回true。如果栈为空,则返回false。 
上述代码中,使用了 MAX 和 Item,它们可能是在头文件 stack.h 中定义的常量和类型。根据上下文来看,MAX 可能表示栈的最大容量,而 Item 可能表示栈中存储的元素类型。
这段代码实现了一个基本的栈数据结构,可以用来存储一组数据,并按照后进先出(LIFO)的方式进行操作。根据需要,可以将其作为其他程序的模块,实现对栈的操作和管理。
// stock00.cpp -- implementing the Stock class
// version 00
#include <iostream>
#include "stock00.h"void Stock::acquire(const std::string & co, long n, double pr)
{company = co;if (n < 0){std::cout << "Number of shares can't be negative; "<< company << " shares set to 0.\n";shares = 0;}elseshares = n;share_val = pr;set_tot();
}void Stock::buy(long num, double price)
{if (num < 0){std::cout << "Number of shares purchased can't be negative. "<< "Transaction is aborted.\n";}else{shares += num;share_val = price;set_tot();}
}void Stock::sell(long num, double price)
{using std::cout;if (num < 0){cout << "Number of shares sold can't be negative. "<< "Transaction is aborted.\n";}else if (num > shares){cout << "You can't sell more than you have! "<< "Transaction is aborted.\n";}else{shares -= num;share_val = price;set_tot();}
}void Stock::update(double price)
{share_val = price;set_tot();
}void Stock::show()
{std::cout << "Company: " << company<< "  Shares: " << shares << '\n'<< "  Share Price: $" << share_val<< "  Total Worth: $" << total_val << '\n';
}
 
这段代码是一个股票(Stock)类的实现,包含了股票类的成员函数的定义。
在这段代码中,Stock 类具有以下成员函数:
- 成员函数 
acquire(const std::string & co, long n, double pr):用于购买股票,给股票的公司名称、股票数量和股票价格赋值。如果股票数量小于 0,则将股票数量设为 0,并输出错误信息。然后通过调用set_tot()函数来计算总价值。 - 成员函数 
buy(long num, double price):用于购买额外的股票,给股票数量和股票价格赋值。如果购买的股票数量小于 0,则输出错误信息。否则,将购买的股票数量加到原有股票数量上,并更新股票价格,然后通过调用set_tot()函数来计算总价值。 - 成员函数 
sell(long num, double price):用于卖出股票,给卖出的股票数量和股票价格赋值。如果卖出的股票数量小于 0,则输出错误信息。如果卖出的股票数量大于持有的股票数量,则输出错误信息。否则,将卖出的股票数量从持有的股票数量中减去,并更新股票价格,然后通过调用set_tot()函数来计算总价值。 - 成员函数 
update(double price):用于更新股票价格,将新的股票价格赋值,并通过调用set_tot()函数来计算总价值。 - 成员函数 
show():用于显示股票的信息,包括公司名称、持有的股票数量、股票价格和总价值。 
这段代码实现了一个简单的股票类,可以用来管理股票的相关信息,包括购买、卖出、更新股票价格和显示股票信息等功能。可以根据需要在程序中创建股票对象并调用相应的成员函数来实现对股票的操作和管理。
类的构造函数和析构函数
类的构造函数是一种特殊的成员函数,用于创建类的对象并初始化其成员变量。构造函数的名称与类名相同,没有返回类型,并且可以具有参数。
构造函数在对象创建时自动调用,并负责初始化对象的状态。它可以执行一些必要的设置操作,如分配内存、初始化成员变量、打开文件等。如果没有显式定义构造函数,编译器会提供一个默认的构造函数。
析构函数也是一种特殊的成员函数,它与构造函数相反。析构函数的名称与类名相同,但前面加上波浪号(~)作为前缀,没有返回类型,也不带参数。
析构函数在对象销毁时自动调用,负责释放对象所占用的资源,如关闭文件、释放内存等。与构造函数一样,如果没有显式定义析构函数,编译器也会提供一个默认的析构函数。
构造函数和析构函数在类的生命周期中起到重要的作用,构造函数用于初始化对象,而析构函数用于清理对象。它们的定义和实现根据具体的需求来决定。
当一个类被实例化为对象时,构造函数会被调用来初始化对象的成员变量。构造函数可以重载,即在一个类中可以定义多个构造函数,每个构造函数可以有不同的参数列表,用于满足不同的对象创建需求。
以下是一个示例:
class MyClass {
private:int x;int y;public:// 默认构造函数MyClass() {x = 0;y = 0;}// 带参数的构造函数MyClass(int a, int b) {x = a;y = b;}
};
 
在上面的示例中,MyClass 类定义了两个构造函数:默认构造函数和带参数的构造函数。默认构造函数没有参数,用于创建一个具有默认初始值的对象。带参数的构造函数接受两个整数参数,用于创建一个对象并初始化成员变量。
当对象被销毁时,析构函数会被调用来清理对象所占用的资源。析构函数通常用于释放动态分配的内存、关闭打开的文件等清理操作。
以下是一个示例:
class MyClass {
private:int* data;public:// 构造函数MyClass() {data = new int[10];}// 析构函数~MyClass() {delete[] data;}
};
 
在上面的示例中,MyClass 类的构造函数动态分配了一个整数数组,并在析构函数中释放了该数组所占用的内存,以防止内存泄漏。
需要注意的是,析构函数通常不需要显式调用,它会在对象被销毁时自动调用。当对象的作用域结束或delete操作符被用于释放对象时,析构函数会被自动调用。
// stock1.cpp – Stock class implementation with constructors, destructor added
#include <iostream>
#include "stock10.h"// constructors (verbose versions)
Stock::Stock()        // default constructor
{std::cout << "Default constructor called\n";company = "no name";shares = 0;share_val = 0.0;total_val = 0.0;
}Stock::Stock(const std::string & co, long n, double pr)
{std::cout << "Constructor using " << co << " called\n";company = co;if (n < 0){std::cout << "Number of shares can't be negative; "<< company << " shares set to 0.\n";shares = 0;}elseshares = n;share_val = pr;set_tot();
}
// class destructor
Stock::~Stock()        // verbose class destructor
{std::cout << "Bye, " << company << "!\n";
}// other methods
void Stock::buy(long num, double price)
{if (num < 0){std::cout << "Number of shares purchased can't be negative. "<< "Transaction is aborted.\n";}else{shares += num;share_val = price;set_tot();}
}void Stock::sell(long num, double price)
{using std::cout;if (num < 0){cout << "Number of shares sold can't be negative. "<< "Transaction is aborted.\n";}else if (num > shares){cout << "You can't sell more than you have! "<< "Transaction is aborted.\n";}else{shares -= num;share_val = price;set_tot();}
}void Stock::update(double price)
{share_val = price;set_tot();
}void Stock::show()
{using std::cout;using std::ios_base;// set format to #.###ios_base::fmtflags orig = cout.setf(ios_base::fixed, ios_base::floatfield); std::streamsize prec = cout.precision(3);cout << "Company: " << company<< "  Shares: " << shares << '\n';cout << "  Share Price: $" << share_val;// set format to #.##cout.precision(2);cout << "  Total Worth: $" << total_val << '\n';// restore original formatcout.setf(orig, ios_base::floatfield);cout.precision(prec);
}
 
更详细地解释一下类的构造函数和析构函数
构造函数是一种特殊的成员函数,在创建一个对象时被调用,用于初始化对象的成员变量。它的名称与类的名称相同,并且没有返回类型(甚至没有void)。构造函数可以有多个重载版本,每个版本可以接受不同的参数。这样,我们可以根据需要创建不同类型的对象。
在给定的代码中,类Stock定义了两个构造函数:
-  
默认构造函数:
Stock::Stock()
这是没有参数的构造函数,用于创建一个空白的Stock对象。在构造函数中,它首先输出一条消息,然后将company设置为默认值"no name",将shares、share_val和total_val都设置为0。 -  
带参数的构造函数:
Stock::Stock(const std::string & co, long n, double pr)
这个构造函数接受三个参数:co表示公司名称,n表示股票数量,pr表示股价。在构造函数中,它首先输出一条消息,然后将传入的公司名称赋值给company。如果传入的股票数量n小于0,则输出错误信息并将shares设置为0。否则,将传入的股票数量赋值给shares,将传入的股价赋值给share_val,并调用set_tot()方法计算总价值。 
析构函数是一个特殊的成员函数,没有参数和返回类型(也没有void)。析构函数在对象被销毁时自动调用,用于清理对象分配的资源。在给定的代码中,析构函数的定义为Stock::~Stock()。在析构函数中,它输出一条包含公司名称的消息。
总结起来,构造函数用于初始化对象的成员变量,而析构函数用于清理对象分配的资源。它们都是类的重要组成部分,帮助我们在创建和销毁对象时执行所需的操作。
// usestok1.cpp -- using the Stock class
// compile with stock10.cpp
#include <iostream>
#include "stock10.h"int main()
{{using std::cout;cout << "Using constructors to create new objects\n";Stock stock1("NanoSmart", 12, 20.0);            // syntax 1stock1.show();Stock stock2 = Stock ("Boffo Objects", 2, 2.0); // syntax 2stock2.show();cout << "Assigning stock1 to stock2:\n";stock2 = stock1;cout << "Listing stock1 and stock2:\n";stock1.show();stock2.show();cout << "Using a constructor to reset an object\n";stock1 = Stock("Nifty Foods", 10, 50.0);    // temp objectcout << "Revised stock1:\n";stock1.show();cout << "Done\n";}// std::cin.get();return 0; 
}
 
这段代码展示了如何使用Stock类。让我逐行解释代码的功能。
#include <iostream>
#include "stock10.h"
 
这里引入了必要的头文件。
int main()
{{using std::cout;cout << "Using constructors to create new objects\n";
 
在main函数开始时,使用了一个内部块{}。这样做是为了定义一个范围,以便后面的对象在范围结束时被销毁。using语句用于引入命名空间std中的cout符号。然后输出一条消息。
    Stock stock1("NanoSmart", 12, 20.0);            // syntax 1stock1.show();Stock stock2 = Stock ("Boffo Objects", 2, 2.0); // syntax 2stock2.show();
 
创建了两个Stock对象,分别命名为stock1和stock2,并使用不同的构造函数。通过两种语法方式进行对象的初始化,第一种使用了直接初始化,第二种使用了拷贝初始化。然后调用show()方法显示对象的详细信息。
    cout << "Assigning stock1 to stock2:\n";stock2 = stock1;cout << "Listing stock1 and stock2:\n";stock1.show();stock2.show();
 
将stock1赋值给stock2,这里使用了赋值运算符重载。然后分别调用show()方法显示stock1和stock2的详细信息。
    cout << "Using a constructor to reset an object\n";stock1 = Stock("Nifty Foods", 10, 50.0);    // temp objectcout << "Revised stock1:\n";stock1.show();cout << "Done\n";}// std::cin.get();return 0; 
}
 
使用临时对象,通过构造函数重置了stock1对象。然后调用show()方法显示更新后的stock1的详细信息。最后输出一条消息,并结束main函数。
this指针
this指针是一个隐含在每个非静态成员函数内部的特殊指针。它指向调用该成员函数的对象本身。通过this指针,我们可以在成员函数中访问对象的成员变量和成员函数。
在C++中,当你调用一个对象的成员函数时,编译器会自动传入一个隐藏的参数,即this指针。在成员函数内部,你可以使用this指针来访问对象的成员。
例如,假设有一个类Foo,并且有一个非静态成员函数bar,那么在bar函数内部,你可以使用this->来引用当前对象的成员。例如,this->x将访问对象的成员变量x,this->fun()将调用对象的成员函数fun()。
this指针的主要作用是在成员函数中区分局部变量和成员变量,以及在成员函数中传递对象本身的引用。在大多数情况下,你可以省略this指针的使用,因为它是隐含的。
需要注意的是,this指针不适用于静态成员函数,因为静态成员函数不与任何特定对象相关联,而是与类本身关联。
当使用继承关系时,this指针的行为也会相应地进行调整。
在继承关系中,派生类继承了基类的成员函数和成员变量。当你在派生类的成员函数中使用this指针时,它将指向当前正在调用该成员函数的派生类对象。
如果在派生类中的成员函数中调用基类的同名成员函数,你可以使用this->基类::成员函数名()来显式地调用基类的成员函数。
此外,在派生类中,通过this指针可以访问基类的成员变量和成员函数。例如,this->基类::成员变量名可以访问基类的成员变量,this->基类::成员函数名()可以调用基类的成员函数。
需要注意的是,如果派生类中定义了与基类同名的成员变量或成员函数,那么在派生类成员函数中使用this指针时,将优先访问派生类的成员。
继承关系中的this指针可以帮助我们在派生类中操作基类和派生类的成员,提供更灵活的编程方式。
// stock20.cpp -- augmented version
#include <iostream>
#include "stock20.h"
using namespace std;
// constructors
Stock::Stock()        // default constructor
{shares = 0;share_val = 0.0;total_val = 0.0;
}Stock::Stock(const std::string & co, long n, double pr)
{company = co;if (n < 0){std::cout << "Number of shares can't be negative; "<< company << " shares set to 0.\n";shares = 0;}elseshares = n;share_val = pr;set_tot();
}// class destructor
Stock::~Stock()        // quiet class destructor
{
}// other methods
void Stock::buy(long num, double price)
{if (num < 0){std::cout << "Number of shares purchased can't be negative. "<< "Transaction is aborted.\n";}else{shares += num;share_val = price;set_tot();}
}void Stock::sell(long num, double price)
{using std::cout;if (num < 0){cout << "Number of shares sold can't be negative. "<< "Transaction is aborted.\n";}else if (num > shares){cout << "You can't sell more than you have! "<< "Transaction is aborted.\n";}else{shares -= num;share_val = price;set_tot();}
}void Stock::update(double price)
{share_val = price;set_tot();
}void Stock::show() const
{using std::cout;using std::ios_base;// set format to #.###ios_base::fmtflags orig = cout.setf(ios_base::fixed, ios_base::floatfield); std::streamsize prec = cout.precision(3);cout << "Company: " << company<< "  Shares: " << shares << '\n';cout << "  Share Price: $" << share_val;// set format to #.##cout.precision(2);cout << "  Total Worth: $" << total_val << '\n';// restore original formatcout.setf(orig, ios_base::floatfield);cout.precision(prec);
}const Stock & Stock::topval(const Stock & s) const
{if (s.total_val > total_val)return s;elsereturn *this; 
}
 
这段代码是一个增强版本的股票管理程序,实现了一个Stock类。以下是每个函数的功能:
-  
默认构造函数
Stock::Stock():初始化股票对象的成员变量,默认将shares、share_val和total_val都设置为0.0。 -  
带参数的构造函数
Stock::Stock(const std::string & co, long n, double pr):使用传入的参数来初始化股票对象的成员变量。其中,co表示公司名称,n表示股票数量,pr表示股票价格。如果股票数量小于0,会将其重置为0,并显示错误提示信息。 -  
析构函数
Stock::~Stock():空函数体的析构函数。 -  
void Stock::buy(long num, double price):购买股票的函数。如果购买的股票数量小于0,会显示错误信息;否则,会更新股票数量、股票价格并计算总价值。 -  
void Stock::sell(long num, double price):卖出股票的函数。如果卖出的股票数量小于0,会显示错误信息;如果卖出的股票数量大于已有的股票数量,会显示错误信息;否则,会更新股票数量、股票价格并计算总价值。 -  
void Stock::update(double price):更新股票价格的函数,用于给股票设置新的价格,并计算总价值。 -  
void Stock::show() const:显示股票信息的函数。会将公司名称、股票数量、股票价格以及总价值显示出来。 -  
const Stock & Stock::topval(const Stock & s) const:比较两个股票对象的价值,并返回价值较大的那个对象。 
## 对象数组
 如果你想要创建一个存储多个股票对象的数组,可以像下面这样进行操作:
#include <iostream>
#include "stock20.h"const int MAX_STOCKS = 5; // 数组的最大大小int main()
{Stock stocks[MAX_STOCKS]; // 创建一个大小为MAX_STOCKS的Stock对象数组// 初始化股票数组的元素stocks[0] = Stock("Company1", 100, 10.0);stocks[1] = Stock("Company2", 200, 20.0);stocks[2] = Stock("Company3", 300, 30.0);stocks[3] = Stock("Company4", 400, 40.0);stocks[4] = Stock("Company5", 500, 50.0);// 使用循环输出股票信息for (int i = 0; i < MAX_STOCKS; i++){stocks[i].show();std::cout << std::endl;}return 0;
}
 
在上面的示例中,我们首先定义了一个常量MAX_STOCKS来表示数组的最大大小,然后创建了一个名为stocks的Stock对象数组,大小为MAX_STOCKS。接下来,我们使用赋值语句将每个数组元素初始化为一个不同的Stock对象。
最后,使用for循环遍历数组,并针对每个数组元素调用show函数来显示股票信息。
// usestok2.cpp -- using the Stock class
// compile with stock20.cpp
#include <iostream>
#include "stock20.h"const int STKS = 4;
int main()
{{
// create an array of initialized objectsStock stocks[STKS] = {Stock("NanoSmart", 12, 20.0),Stock("Boffo Objects", 200, 2.0),Stock("Monolithic Obelisks", 130, 3.25),Stock("Fleep Enterprises", 60, 6.5)};std::cout << "Stock holdings:\n";int st;for (st = 0; st < STKS; st++)stocks[st].show();
// set pointer to first elementconst Stock * top = &stocks[0];for (st = 1; st < STKS; st++)top = &top->topval(stocks[st]);
// now top points to the most valuable holdingstd::cout << "\nMost valuable holding:\n";top->show();}// std::cin.get();return 0; 
}
 
这段代码是使用Stock类的示例代码。它包括了对Stock类的创建对象、访问成员函数以及使用指针操作对象的示范。具体代码如下:
#include <iostream>
#include "stock20.h"const int STKS = 4;int main()
{// 创建一个已初始化的对象数组Stock stocks[STKS] = {Stock("NanoSmart", 12, 20.0),Stock("Boffo Objects", 200, 2.0),Stock("Monolithic Obelisks", 130, 3.25),Stock("Fleep Enterprises", 60, 6.5)};std::cout << "Stock holdings:\n";int st;for (st = 0; st < STKS; st++)stocks[st].show();// 设置指针指向第一个元素const Stock *top = &stocks[0];for (st = 1; st < STKS; st++)top = &top->topval(stocks[st]);// 现在top指向价值最高的持股std::cout << "\nMost valuable holding:\n";top->show();return 0;
}
 
代码首先创建了一个名为stocks的Stock对象数组,数组中存储了四个已经初始化的Stock对象。然后使用循环遍历数组,并调用每个对象的show()函数显示股票信息。
接下来,定义了一个指向Stock对象的常量指针top,将其初始化为指向数组中第一个元素的地址。然后使用循环比较每个对象的价值,并更新指针top,使其指向价值最高的持股。
最后,通过调用top->show()函数,显示了价值最高的持股的信息。
类作用域
对不起,我之前的回答有误。在C++中,没有类作用域(class scope)这个概念。C++中的作用域是由块作用域(block scope)、命名空间作用域(namespace scope)和文件作用域(file scope)组成的。
类定义中声明的成员变量和成员函数的作用域是在类外访问它们的作用域,而不是在类内部的作用域。
具体来说,成员变量和成员函数的作用域可以分为两个部分:
- 类的外部作用域:在类外部,使用类名和成员名来访问成员变量和成员函数。需要使用作用域运算符
::来指明成员所属的类。 
示例代码:
class MyClass {
public:int memberVar; // 成员变量void memberFunc(); // 成员函数
};void MyClass::memberFunc() {// 成员函数的实现
}int main() {MyClass obj;obj.memberVar = 10; // 在类的外部访问成员变量obj.memberFunc(); // 在类的外部调用成员函数
}
 
- 类内部作用域:在类内部,可以直接访问成员变量和成员函数,不需要使用类名或对象实例。
 
示例代码:
class MyClass {
public:int memberVar; // 成员变量void memberFunc() {memberVar = 10; // 在类的内部直接访问成员变量}
};int main() {MyClass obj;obj.memberFunc(); // 调用成员函数,在类的内部直接访问成员变量
}
 
需要注意的是,成员变量和成员函数的访问权限由访问修饰符(public、private、protected)控制,而不是作用域控制。
对于成员函数的定义,可以在类的内部直接定义,也可以在类外部定义。对于成员变量,只能在类的内部进行定义。
在C++中,类确实有自己的作用域。
在类定义的作用域内声明的名称在类外是不可见的,但在类的成员函数内部可以直接访问这些名称。
例如:
class MyClass {
public:int memberVar; // 成员变量void memberFunc() {memberVar = 10; // 在类的成员函数内可以直接访问成员变量int localVar = 20; // 在类的成员函数内可以声明局部变量}
};int main() {MyClass obj;obj.memberVar = 5; // 在类外部无法直接访问成员变量obj.memberFunc(); // 在类外部调用成员函数
}
 
在上面的例子中,memberVar是MyClass类的一个成员变量,它只能在类的作用域内直接访问。在memberFunc()成员函数内部,就可以直接访问memberVar。而在main()函数中,我们只能通过类的对象来访问成员变量和成员函数。
抽象数据类型
抽象数据类型(Abstract Data Type,简称ADT)是一种计算机科学中的概念,用于描述一个数据对象以及对该对象进行操作的集合。
ADT 定义了数据类型的行为,但不涉及具体的实现细节。它将数据类型的表示和操作封装起来,提供了一种独立于具体实现的抽象层次,使得用户可以使用这个数据类型而无需关心实现的细节。
一个抽象数据类型包括以下两个主要部分:
-  
数据表示(Data Representation):描述数据对象的内部表示,可以通过数据结构(如数组、链表、树等)来实现。
 -  
操作集合(Operations):定义了对数据对象进行操作的接口,包括一组允许用户使用的操作或函数。这些操作可以包括创建、销毁、访问和修改数据对象等。
 
举个例子,我们可以考虑一个抽象数据类型"栈"(Stack)。栈可以用数组或链表来实现,但是在ADT的定义中,我们只关注它的基本操作:
- 初始化(Initialize):初始化一个空的栈。
 - 入栈(Push):将一个元素压入栈顶。
 - 出栈(Pop):从栈顶弹出一个元素。
 - 获取栈顶元素(Top):返回栈顶元素。
 - 判断栈是否为空(IsEmpty):检查栈是否为空。
 - 清空栈(Clear):清空栈中的所有元素。
 
通过这些操作,我们可以使用栈来实现一系列功能,如括号匹配、逆波兰表达式求值等等。在使用栈时,我们不需要关心底层的具体实现方式,只需要调用操作集合中定义好的函数即可。
抽象数据类型提供了封装和抽象化的方法,能够帮助程序员设计更加清晰、模块化的程序,并提高代码的可重用性和维护性。
在C++中,可以使用类(class)来实现抽象数据类型。类是一种用户自定义的数据类型,它可以封装数据成员和成员函数,以便提供更高层次的抽象。
以下是使用C++类实现抽象数据类型的基本步骤:
- 定义类:使用关键字
class来定义一个类,并在类中声明数据成员和成员函数。例如: 
class Stack {
private:int* data;int top;int capacity;
public:Stack(int size);  // 构造函数~Stack();         // 析构函数void push(int element);int pop();int topElement();bool isEmpty();void clear();
};
 
在上面的例子中,我们定义了一个名为Stack的类,它具有私有数据成员data、top和capacity,以及公有成员函数push、pop、topElement、isEmpty和clear。
- 实现类的成员函数:类的成员函数可以在类的内部定义,或在类的外部进行定义。例如:
 
// 在类的内部定义成员函数
Stack::Stack(int size) {data = new int[size];top = -1;capacity = size;
}// 在类的外部定义其他成员函数
void Stack::push(int element) {if (top + 1 == capacity) {cout << "Stack is full!" << endl;} else {data[++top] = element;}
}// ...
 
在上面的例子中,我们在类的内部定义了构造函数Stack::Stack(int size),用于初始化栈的大小。而其他成员函数如push则在类的外部进行定义。
- 创建对象并使用:在主程序中,可以创建类的对象,并使用对象调用类的成员函数。例如:
 
int main() {Stack stack(10);  // 创建一个大小为10的栈对象stack.push(5);   // 入栈操作stack.push(8);int topElement = stack.topElement();  // 获取栈顶元素cout << "Top element: " << topElement << endl;stack.pop();     // 出栈操作bool empty = stack.isEmpty();  // 检查栈是否为空cout << "Is stack empty? " << (empty ? "Yes" : "No") << endl;stack.clear();   // 清空栈return 0;
}
 
在上面的例子中,我们创建了一个名为stack的Stack对象,并通过对象调用了类的成员函数,如push、topElement、pop等。
使用类实现抽象数据类型可以使代码更加模块化和易于维护,同时提供了数据和操作的封装,以及对外界的隐藏。这样可以提高代码的可读性、可靠性和可重用性。
希望这能够帮助你对C++中使用类实现抽象数据类型有更好的理解。如有需要,请继续提问。
// stacker.cpp -- testing the Stack class
#include <iostream>
#include <cctype>  // or ctype.h
#include "stack.h"
int main()
{using namespace std;Stack st; // create an empty stackchar ch;unsigned long po;cout << "Please enter A to add a purchase order,\n"<< "P to process a PO, or Q to quit.\n";while (cin >> ch && toupper(ch) != 'Q'){while (cin.get() != '\n')   continue;if (!isalpha(ch)){cout << '\a';continue;}switch(ch){case 'A':case 'a': cout << "Enter a PO number to add: ";cin >> po;if (st.isfull())cout << "stack already full\n";elsest.push(po);break;case 'P':case 'p': if (st.isempty())cout << "stack already empty\n";else {st.pop(po);cout << "PO #" << po << " popped\n";}break;}cout << "Please enter A to add a purchase order,\n"<< "P to process a PO, or Q to quit.\n";}cout << "Bye\n";return 0; 
}
 
这段代码是一个使用Stack类的示例程序,用于模拟添加和处理购买订单。代码中使用了一个循环来接受用户输入的指令,并根据指令执行相应的操作。
在程序开始部分,首先包含了一些所需的头文件,其中<iostream>用于输入输出操作,<cctype>用于字符处理操作。还包含了一个名为"stack.h"的头文件,用于定义Stack类。
在main函数中,首先创建了一个名为st的Stack对象,这个对象用于存储购买订单。然后使用一个循环来接受用户的输入指令。
在每次循环迭代中,首先输出提示信息让用户选择操作。然后通过cin读取用户输入的一个字符并存储在变量ch中。接下来使用toupper函数将字符转换为大写,并与字符’Q’进行比较,判断用户是否输入了’Q’来退出程序。
如果用户没有输入’Q’,则进入内层循环。这个循环会消耗掉用户输入缓冲区中的字符,以防止这些字符对后续的输入造成干扰。
然后检查用户输入的字符是否为字母,如果不是字母,则输出警告声音并继续下一次循环。
如果用户输入的字符是’A’或’a’,则要求用户输入一个整数作为购买订单编号,并通过cin将其存储在变量po中。然后通过调用st.isfull()函数检查栈是否已满,如果满了则输出提示信息,否则通过调用st.push(po)向栈中添加购买订单。
如果用户输入的字符是’P’或’p’,则先通过调用st.isempty()函数检查栈是否为空,如果为空则输出提示信息,否则通过调用st.pop(po)函数从栈中弹出一个购买订单编号,并输出相应的信息。
在每次循环迭代结束时,再次输出提示信息,让用户继续选择操作。
当用户输入’Q’时,循环结束,输出"Bye"并返回0表示程序正常退出。
相关文章:
C++ Primer Plus 第6版 读书笔记(10) 第十章 类与对象
第十章 类与对象 在面向对象编程中,类和对象是两个重要的概念。 类(Class)是一种用户自定义的数据类型,用于封装数据和操作。它是对象的模板或蓝图,描述了对象的属性(成员变量)和行为…...
基于C++ 的OpenCV绘制多边形,多边形多条边用不用的颜色绘制
使用基于C的OpenCV库来绘制多边形,并且为多边形的不同边使用不同的颜色,可以按照以下步骤进行操作: 首先,确保你已经安装了OpenCV库并配置好了你的开发环境。 导入必要的头文件: #include <opencv2/opencv.hpp&g…...
(六)、深度学习框架中的算子
1、深度学习框架算子的基本概念 深度学习框架中的算子(operator)是指用于执行各种数学运算和操作的函数或类。这些算子通常被用来构建神经网络的各个层和组件,实现数据的传递、转换和计算。 算子是深度学习模型的基本组成单元,它们…...
Redis实现共享Session
Redis实现共享Session 分布式系统中,sessiong共享有很多的解决方案,其中托管到缓存中应该是最常用的方案之一。 1、引入依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM…...
网络通信原理UDP协议(第五十课)
UDP协议:用户数据包协议,无连接、不可靠,效率高 字段长度描述Source Port2字节标识哪个应用程序发送(发送进程)。Destination Port2字节标识哪个应用程序接收(接收进程)。Length2字节UDP首部加上UDP数据的字节数,最小为8。Checksum2字节覆盖UDP首部和UDP数据,是可…...
43、TCP报文(一)
本节内容开始,我们正式学习TCP协议中具体的一些原理。首先,最重要的内容仍然是这个协议的封装结构和首部格式,因为这里面牵扯到一些环环相扣的知识点,例如ACK、SYN等等,如果这些内容不能很好的理解,那么后续…...
【JavaScript】使用js实现滑块验证码功能与浏览器打印
滑块验证码 效果图: 实现思路: 根据滑块的最左侧点跟最右侧点, 是否在规定的距离内【页面最左侧为原点】,来判断是否通过 html代码: <!DOCTYPE html> <html><head><title>滑动图片验证码&…...
【使用群晖远程链接drive挂载电脑硬盘】
文章目录 前言1.群晖Synology Drive套件的安装1.1 安装Synology Drive套件1.2 设置Synology Drive套件1.3 局域网内电脑测试和使用 2.使用cpolar远程访问内网Synology Drive2.1 Cpolar云端设置2.2 Cpolar本地设置2.3 测试和使用 3. 结语 前言 群晖作为专业的数据存储中心&…...
easyx图形库基础4:贪吃蛇
贪吃蛇 一实现贪吃蛇:1.绘制网格:1.绘制蛇:3.控制蛇的默认移动向右:4.控制蛇的移动方向:5.生成食物6.判断蛇吃到食物并且长大。7.判断游戏结束:8.重置函数: 二整体代码: 一实现贪吃蛇…...
哈夫曼树(赫夫曼树、最优树)详解
目录 哈夫曼树(赫夫曼树、最优树)详解 哈夫曼树相关的几个名词 什么是哈夫曼树 构建哈夫曼树的过程 哈弗曼树中结点结构 构建哈弗曼树的算法实现 哈夫曼树(赫夫曼树、最优树)详解 哈夫曼树相关的几个名词 路径:…...
智慧建筑工地平台,通过信息化技术、物联网、人工智能技术,实现对施工全过程的实时监控、数据分析、智能管理和优化调控
智慧工地是指通过信息化技术、物联网、人工智能技术等手段,对建筑工地进行数字化、智能化、网络化升级,实现对施工全过程的实时监控、数据分析、智能管理和优化调控。智慧工地的建设可以提高工地的安全性、效率性和质量,降低施工成本…...
Springboot 实践(8)springboot集成Oauth2.0授权包,对接spring security接口
此文之前,项目已经添加了数据库DAO服务接口、资源访问目录、以及数据访问的html页面,同时项目集成了spring security,并替换了登录授权页面;但是,系统用户存储代码之中,而且只注册了admin和user两个用户。在…...
OpenCV-Python中的图像处理-GrabCut算法交互式前景提取
OpenCV-Python中的图像处理-GrabCut算法交互式前景提取 Python-OpenCV中的图像处理-GrabCut算法交互式前景提取 Python-OpenCV中的图像处理-GrabCut算法交互式前景提取 cv2.grabCut(img: Mat, mask: typing.Optional[Mat], rect, bgdModel, fgdModel, iterCount, mode…) img…...
leetcode原题 后继者:找出二叉搜索树中指定节点的“下一个”节点
题目: 设计一个算法,找出二叉搜索树中指定节点的“下一个”节点(也即中序后继)。 如果指定节点没有对应的“下一个”节点,则返回null。 示例: 输入: root [2,1,3], p 1 2 / \ 1 3 输出: 2 解题思路…...
pyqt5 QlineEdit 如何设置只能输入数字
在 PyQt(Python中的一个GUI库)中,可以使用QLineEdit小部件的setValidator()方法来限制用户输入的内容。要让QLineEdit只能输入数字,你可以使用QIntValidator或QDoubleValidator。下面是一个示例代码,展示如何设置只能输…...
ubuntu中安装python
最简单方便的是 apt 使用第三方的 ppa 源,然后直接 apt 安装 python3.9 安装 software-properties-common 获取add-apt-repository命令:apt install -y software-properties-common添加第三方的 ppa 源:add-apt-repository ppa:deadsnakes/p…...
LeetCode150道面试经典题-- 快乐数(简单)
1.题目 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为: 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果这个过程 结果为 1&am…...
科研论文配图----第一章笔记
第一章笔记 科研论文的绘制基础 科研论文配图的分类与构成 根据呈现方式,科研论文配图可分为线性图、灰度图、照片彩图和综合配图 4 种类型。 其中,线性图是主要和常用的配图类型,也是本书重点介绍的配图类型。 科研论文配图的格式和尺寸 格…...
OpenHarmony Meetup 广州站 OpenHarmony正当时—技术开源
招募令 OpenHarmony Meetup 广州站 火热招募中,等待激情四射的开发者,线下参与OpenHarmonyMeetup线下交流 展示前沿技术、探讨未来可能、让你了解更多专属OpenHarmony的魅力 线下参与,先到先得,仅限20个名额! 报名截止时间8月23日…...
如何使用PHP Smarty模板实现静态页面生成
首先,你需要从Smarty官网下载这个神奇的文件。然后,你需要在你的PHP文件中引入Smarty类。就像这样: require_once(Smarty.class.php);现在,我们要创建一个Smarty实例。这就像打开一个新的文件,只不过这个文件是可以和…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...
突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
UE5 学习系列(三)创建和移动物体
这篇博客是该系列的第三篇,是在之前两篇博客的基础上展开,主要介绍如何在操作界面中创建和拖动物体,这篇博客跟随的视频链接如下: B 站视频:s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...
Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...
安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...
