第十章 类与对象
在面向对象编程中,类和对象是两个重要的概念。
类(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; } else return false; } bool Stack::pop(Item & item) { if (top > 0) { item = items[--top]; return true; } else return 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; } else shares = 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; } else shares = 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 format cout.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 1 stock1.show(); Stock stock2 = Stock ("Boffo Objects", 2, 2.0); // syntax 2 stock2.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 object cout << "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 1 stock1.show(); Stock stock2 = Stock ("Boffo Objects", 2, 2.0); // syntax 2 stock2.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 object cout << "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; } else shares = 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 format cout.setf(orig, ios_base::floatfield); cout.precision(prec); } const Stock & Stock::topval(const Stock & s) const { if (s.total_val > total_val) return s; else return *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 objects 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(); // set pointer to first element const Stock * top = &stocks[0]; for (st = 1; st < STKS; st++) top = &top->topval(stocks[st]); // now top points to the most valuable holding std::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 stack char 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"; else st.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表示程序正常退出。