当前位置: 首页 > news >正文

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 的类。这个类有两个成员变量 nameage,以及一个成员函数 display()。然后,在 main() 函数中,我们分别创建了两个 Person 类的对象 person1person2,并给它们的成员变量赋值。最后,我们通过调用对象的成员函数 display() 来展示对象的属性。

输出结果为:

Name: Alice, Age: 25
Name: Bob, Age: 30

通过类和对象,我们可以更加方便地组织和管理数据,并定义与数据相关的操作,实现面向对象编程的特性。

当一个类从另一个类派生出来时,我们称之为继承(Inheritance)。通过继承,子类可以继承父类的属性和行为,并且可以在此基础上添加新的属性和行为,从而实现代码的重用性和扩展性。

有两种常见的继承关系:

  1. 单继承(Single Inheritance):一个子类只能继承一个父类。
  2. 多继承(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 和一个派生类 StudentStudent 类使用关键字 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

上述代码中,使用了 MAXItem,它们可能是在头文件 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",将sharesshare_valtotal_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对象,分别命名为stock1stock2,并使用不同的构造函数。通过两种语法方式进行对象的初始化,第一种使用了直接初始化,第二种使用了拷贝初始化。然后调用show()方法显示对象的详细信息。

    cout << "Assigning stock1 to stock2:\n";stock2 = stock1;cout << "Listing stock1 and stock2:\n";stock1.show();stock2.show();

stock1赋值给stock2,这里使用了赋值运算符重载。然后分别调用show()方法显示stock1stock2的详细信息。

    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将访问对象的成员变量xthis->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():初始化股票对象的成员变量,默认将sharesshare_valtotal_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来表示数组的最大大小,然后创建了一个名为stocksStock对象数组,大小为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;
}

代码首先创建了一个名为stocksStock对象数组,数组中存储了四个已经初始化的Stock对象。然后使用循环遍历数组,并调用每个对象的show()函数显示股票信息。

接下来,定义了一个指向Stock对象的常量指针top,将其初始化为指向数组中第一个元素的地址。然后使用循环比较每个对象的价值,并更新指针top,使其指向价值最高的持股。

最后,通过调用top->show()函数,显示了价值最高的持股的信息。

类作用域

对不起,我之前的回答有误。在C++中,没有类作用域(class scope)这个概念。C++中的作用域是由块作用域(block scope)、命名空间作用域(namespace scope)和文件作用域(file scope)组成的。

类定义中声明的成员变量和成员函数的作用域是在类外访问它们的作用域,而不是在类内部的作用域。

具体来说,成员变量和成员函数的作用域可以分为两个部分:

  1. 类的外部作用域:在类外部,使用类名和成员名来访问成员变量和成员函数。需要使用作用域运算符::来指明成员所属的类。

示例代码:

class MyClass {
public:int memberVar; // 成员变量void memberFunc(); // 成员函数
};void MyClass::memberFunc() {// 成员函数的实现
}int main() {MyClass obj;obj.memberVar = 10; // 在类的外部访问成员变量obj.memberFunc(); // 在类的外部调用成员函数
}
  1. 类内部作用域:在类内部,可以直接访问成员变量和成员函数,不需要使用类名或对象实例。

示例代码:

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(); // 在类外部调用成员函数
}

在上面的例子中,memberVarMyClass类的一个成员变量,它只能在类的作用域内直接访问。在memberFunc()成员函数内部,就可以直接访问memberVar。而在main()函数中,我们只能通过类的对象来访问成员变量和成员函数。

抽象数据类型

抽象数据类型(Abstract Data Type,简称ADT)是一种计算机科学中的概念,用于描述一个数据对象以及对该对象进行操作的集合。

ADT 定义了数据类型的行为,但不涉及具体的实现细节。它将数据类型的表示和操作封装起来,提供了一种独立于具体实现的抽象层次,使得用户可以使用这个数据类型而无需关心实现的细节。

一个抽象数据类型包括以下两个主要部分:

  1. 数据表示(Data Representation):描述数据对象的内部表示,可以通过数据结构(如数组、链表、树等)来实现。

  2. 操作集合(Operations):定义了对数据对象进行操作的接口,包括一组允许用户使用的操作或函数。这些操作可以包括创建、销毁、访问和修改数据对象等。

举个例子,我们可以考虑一个抽象数据类型"栈"(Stack)。栈可以用数组或链表来实现,但是在ADT的定义中,我们只关注它的基本操作:

  • 初始化(Initialize):初始化一个空的栈。
  • 入栈(Push):将一个元素压入栈顶。
  • 出栈(Pop):从栈顶弹出一个元素。
  • 获取栈顶元素(Top):返回栈顶元素。
  • 判断栈是否为空(IsEmpty):检查栈是否为空。
  • 清空栈(Clear):清空栈中的所有元素。

通过这些操作,我们可以使用栈来实现一系列功能,如括号匹配、逆波兰表达式求值等等。在使用栈时,我们不需要关心底层的具体实现方式,只需要调用操作集合中定义好的函数即可。

抽象数据类型提供了封装和抽象化的方法,能够帮助程序员设计更加清晰、模块化的程序,并提高代码的可重用性和维护性。

在C++中,可以使用类(class)来实现抽象数据类型。类是一种用户自定义的数据类型,它可以封装数据成员和成员函数,以便提供更高层次的抽象。

以下是使用C++类实现抽象数据类型的基本步骤:

  1. 定义类:使用关键字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的类,它具有私有数据成员datatopcapacity,以及公有成员函数pushpoptopElementisEmptyclear

  1. 实现类的成员函数:类的成员函数可以在类的内部定义,或在类的外部进行定义。例如:
// 在类的内部定义成员函数
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则在类的外部进行定义。

  1. 创建对象并使用:在主程序中,可以创建类的对象,并使用对象调用类的成员函数。例如:
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;
}

在上面的例子中,我们创建了一个名为stackStack对象,并通过对象调用了类的成员函数,如pushtopElementpop等。

使用类实现抽象数据类型可以使代码更加模块化和易于维护,同时提供了数据和操作的封装,以及对外界的隐藏。这样可以提高代码的可读性、可靠性和可重用性。

希望这能够帮助你对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) 第十章 类与对象

第十章 类与对象 在面向对象编程中&#xff0c;类和对象是两个重要的概念。 类&#xff08;Class&#xff09;是一种用户自定义的数据类型&#xff0c;用于封装数据和操作。它是对象的模板或蓝图&#xff0c;描述了对象的属性&#xff08;成员变量&#xff09;和行为&#xf…...

基于C++ 的OpenCV绘制多边形,多边形多条边用不用的颜色绘制

使用基于C的OpenCV库来绘制多边形&#xff0c;并且为多边形的不同边使用不同的颜色&#xff0c;可以按照以下步骤进行操作&#xff1a; 首先&#xff0c;确保你已经安装了OpenCV库并配置好了你的开发环境。 导入必要的头文件&#xff1a; #include <opencv2/opencv.hpp&g…...

(六)、深度学习框架中的算子

1、深度学习框架算子的基本概念 深度学习框架中的算子&#xff08;operator&#xff09;是指用于执行各种数学运算和操作的函数或类。这些算子通常被用来构建神经网络的各个层和组件&#xff0c;实现数据的传递、转换和计算。 算子是深度学习模型的基本组成单元&#xff0c;它们…...

Redis实现共享Session

Redis实现共享Session 分布式系统中&#xff0c;sessiong共享有很多的解决方案&#xff0c;其中托管到缓存中应该是最常用的方案之一。 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报文(一)

本节内容开始&#xff0c;我们正式学习TCP协议中具体的一些原理。首先&#xff0c;最重要的内容仍然是这个协议的封装结构和首部格式&#xff0c;因为这里面牵扯到一些环环相扣的知识点&#xff0c;例如ACK、SYN等等&#xff0c;如果这些内容不能很好的理解&#xff0c;那么后续…...

【JavaScript】使用js实现滑块验证码功能与浏览器打印

滑块验证码 效果图&#xff1a; 实现思路&#xff1a; 根据滑块的最左侧点跟最右侧点&#xff0c; 是否在规定的距离内【页面最左侧为原点】&#xff0c;来判断是否通过 html代码&#xff1a; <!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:贪吃蛇

贪吃蛇 一实现贪吃蛇&#xff1a;1.绘制网格&#xff1a;1.绘制蛇&#xff1a;3.控制蛇的默认移动向右&#xff1a;4.控制蛇的移动方向&#xff1a;5.生成食物6.判断蛇吃到食物并且长大。7.判断游戏结束&#xff1a;8.重置函数&#xff1a; 二整体代码&#xff1a; 一实现贪吃蛇…...

哈夫曼树(赫夫曼树、最优树)详解

目录 哈夫曼树&#xff08;赫夫曼树、最优树&#xff09;详解 哈夫曼树相关的几个名词 什么是哈夫曼树 构建哈夫曼树的过程 哈弗曼树中结点结构 构建哈弗曼树的算法实现 哈夫曼树&#xff08;赫夫曼树、最优树&#xff09;详解 哈夫曼树相关的几个名词 路径&#xff1a;…...

智慧建筑工地平台,通过信息化技术、物联网、人工智能技术,实现对施工全过程的实时监控、数据分析、智能管理和优化调控

智慧工地是指通过信息化技术、物联网、人工智能技术等手段&#xff0c;对建筑工地进行数字化、智能化、网络化升级&#xff0c;实现对施工全过程的实时监控、数据分析、智能管理和优化调控。智慧工地的建设可以提高工地的安全性、效率性和质量&#xff0c;降低施工成本&#xf…...

Springboot 实践(8)springboot集成Oauth2.0授权包,对接spring security接口

此文之前&#xff0c;项目已经添加了数据库DAO服务接口、资源访问目录、以及数据访问的html页面&#xff0c;同时项目集成了spring security&#xff0c;并替换了登录授权页面&#xff1b;但是&#xff0c;系统用户存储代码之中&#xff0c;而且只注册了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原题 后继者:找出二叉搜索树中指定节点的“下一个”节点

题目&#xff1a; 设计一个算法&#xff0c;找出二叉搜索树中指定节点的“下一个”节点&#xff08;也即中序后继&#xff09;。 如果指定节点没有对应的“下一个”节点&#xff0c;则返回null。 示例&#xff1a; 输入: root [2,1,3], p 1 2 / \ 1 3 输出: 2 解题思路…...

pyqt5 QlineEdit 如何设置只能输入数字

在 PyQt&#xff08;Python中的一个GUI库&#xff09;中&#xff0c;可以使用QLineEdit小部件的setValidator()方法来限制用户输入的内容。要让QLineEdit只能输入数字&#xff0c;你可以使用QIntValidator或QDoubleValidator。下面是一个示例代码&#xff0c;展示如何设置只能输…...

ubuntu中安装python

最简单方便的是 apt 使用第三方的 ppa 源&#xff0c;然后直接 apt 安装 python3.9 安装 software-properties-common 获取add-apt-repository命令&#xff1a;apt install -y software-properties-common添加第三方的 ppa 源&#xff1a;add-apt-repository ppa:deadsnakes/p…...

LeetCode150道面试经典题-- 快乐数(简单)

1.题目 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。如果这个过程 结果为 1&am…...

科研论文配图----第一章笔记

第一章笔记 科研论文的绘制基础 科研论文配图的分类与构成 根据呈现方式&#xff0c;科研论文配图可分为线性图、灰度图、照片彩图和综合配图 4 种类型。 其中&#xff0c;线性图是主要和常用的配图类型&#xff0c;也是本书重点介绍的配图类型。 科研论文配图的格式和尺寸 格…...

OpenHarmony Meetup 广州站 OpenHarmony正当时—技术开源

招募令 OpenHarmony Meetup 广州站 火热招募中&#xff0c;等待激情四射的开发者&#xff0c;线下参与OpenHarmonyMeetup线下交流 展示前沿技术、探讨未来可能、让你了解更多专属OpenHarmony的魅力 线下参与&#xff0c;先到先得,仅限20个名额&#xff01; 报名截止时间8月23日…...

如何使用PHP Smarty模板实现静态页面生成

首先&#xff0c;你需要从Smarty官网下载这个神奇的文件。然后&#xff0c;你需要在你的PHP文件中引入Smarty类。就像这样&#xff1a; require_once(Smarty.class.php);现在&#xff0c;我们要创建一个Smarty实例。这就像打开一个新的文件&#xff0c;只不过这个文件是可以和…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

生成xcframework

打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式&#xff0c;可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

使用VSCode开发Django指南

使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架&#xff0c;专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用&#xff0c;其中包含三个使用通用基本模板的页面。在此…...

通过Wrangler CLI在worker中创建数据库和表

官方使用文档&#xff1a;Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后&#xff0c;会在本地和远程创建数据库&#xff1a; npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库&#xff1a; 现在&#xff0c;您的Cloudfla…...

c#开发AI模型对话

AI模型 前面已经介绍了一般AI模型本地部署&#xff0c;直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型&#xff0c;但是目前国内可能使用不多&#xff0c;至少实践例子很少看见。开发训练模型就不介绍了&am…...

C# 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

管理学院权限管理系统开发总结

文章目录 &#x1f393; 管理学院权限管理系统开发总结 - 现代化Web应用实践之路&#x1f4dd; 项目概述&#x1f3d7;️ 技术架构设计后端技术栈前端技术栈 &#x1f4a1; 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 &#x1f5c4;️ 数据库设…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)

前言&#xff1a; 最近在做行为检测相关的模型&#xff0c;用的是时空图卷积网络&#xff08;STGCN&#xff09;&#xff0c;但原有kinetic-400数据集数据质量较低&#xff0c;需要进行细粒度的标注&#xff0c;同时粗略搜了下已有开源工具基本都集中于图像分割这块&#xff0c…...