Chapter 10 对象和类
1)类规范¶
【1】类声明:数据成员(描述数据部分)+ 成员函数(描述公有接口) 【2】类方法定义:描述如何实现类成员函数
ps: 何为 ”接口“? ![[Pasted image 20230730094108.png]] ![[Pasted image 20230730094124.png]]
2)类的基本成分¶
[1]基本示例:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
[3]访问控制: (1)使用类对象的程序都可以直接访问公有部分 (2)但只能通过公有成员函数( 或 友元函数 )来访问对象的私有成员
ps:类与结构 结构的默认访问类型是public,而类为private
3)实现类的成员函数¶
(1)定义成员函数(也叫作:方法)是用:作用域解析运算符( : : ) 实现的
C++ | |
---|---|
1 2 3 |
|
(2)方法 可以访问类的私有成员,但是非成员函数不可以(友元函数除外!)
C++ | |
---|---|
1 2 3 4 |
|
(3)内联方法 定义位于类声明的函数 都将自动成为 内联函数 eg:上述类Stock中的 Stock : : set_tot( )是一个内联函数
ps : 如果愿意也可以在类声明之外定义成员函数,并使其成为内联函数 方式:在类实现部分定义函数时使用inline限定符即可
C++ 1 2 3 4 5 6 7 8 9 10 11 12class Stock { private: ... void set_tot() // declaration public: ... }; inline void Stock::set_tot() // use inline in definition { total_val = shares*share_val; }
(4)创建 and 使用对象 【1】所创建的每个新对象都有自己的存储空间,用于存储其内部变量和类成员 【2】同一个类的所有对象共享同一组类方法,即:每种方法只有一个副本 eg:kate 和 joe 都是Stock对象,则 kate.shares 占一个内存块, joe.shares 占另一个内存快。但是 kate.show( ) 和 joe.show( ) 都调用同一个方法,也就是说: kate.show( ) 和 joe.show( ) 都执行同一个代码块,只是将这些代码方法用于不同的数据 ![[Pasted image 20230730104821.png]]
4)类的构造函数和析构函数¶
【1】问题引入¶
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
|
【2】构造函数格式¶
C++ | |
---|---|
1 2 3 4 5 6 |
|
【3】使用构造函数初始化对象¶
(1)显式地调用构造函数:
C++ | |
---|---|
1 |
|
(2)隐式地调用构造函数:
C++ | |
---|---|
1 |
|
(poi)创建类对象 + new动态内存分配:
C++ | |
---|---|
1 2 3 4 5 |
|
【4】默认构造函数¶
方法1:给已有的构造函数的所有参数提供默认值
C++ | |
---|---|
1 |
|
方法2:函数重载定义另一个构造函数
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
eg:
C++ | |
---|---|
1 2 3 4 5 6 |
|
【5】析构函数¶
(1)引入:析构函数负责完成清理工作 eg:如果构造函数使用new来分配内存,则析构函数将使用delete来释放这些内存
(2)格式:类名前加上~【跟构造函数一样,没有返回值和声明类型】
poi:析构函数没有参数 eg:Stock类的析构函数原型,必须是: ~Stock()
【6】综合示例分析¶
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// 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);
}
¶
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
|
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 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;
}
¶
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
说明: 1)可以将一个对象赋给另一个对象:
C++ | |
---|---|
1 |
|
默认情况下,给类对象赋值时,将把一个对象复制给另一个
在上面这个例子当中,stock2的原内容将被覆盖!
2)构造函数不仅可用于新对象,也可以用于旧对象的新值赋予
C++ | |
---|---|
1 2 3 4 |
|
3)由函数栈原理:局部变量消失的顺序是:LIFO(自动变量被放在栈中,最后创建的对象最先被删除)
4)区分初始化 与 赋值:
C++ | |
---|---|
1 2 3 |
|
5)对类对象使用 初始化列表 举例说明即可
C++ | |
---|---|
1 2 3 4 5 6 7 8 |
|
6)const成员函数 ![[Pasted image 20230730120542.png]] ![[Pasted image 20230730120553.png]]
5)this指针¶
【1】问题引入:
定义一个成员函数,它查看两个Stock对象,并返回股价较高的那个对象的引用 假设将该方法命名为 topval() 则函数调用stock1.topval( ) 将访问stock1对象的数据 如果希望该方法能对两者进行比较,则应该将第二个对象作为参数传递给它 出于效率,按照引用传递参数!因此:topval( ) 方法将使用一个类型为 const Stock& 的参数
假设要对stock1与stock2进行比较,可以采用:
C++ 1 2 3 4 5写法:const Stock& topval (const Stock& s) const; (1)括号内的const:该函数不会修改被显式访问的对象 (2)括号后的const:该函数不会修改被隐式访问的对象 (3)返回值类型的const:由于该函数返回了2个const对象之一的引用,因此返回类型也应该是const引用
C++ 1 2(方法1) top = stock1.topval(stock2); // 显式访问stock2,隐式访问stock1 (方法2) top = stock2.topval(stock1); // 显式访问stock1,隐式访问stock2
现在问题又来了,注意topval()的实现:
C++ 1 2 3 4 5 6 7 8const Stock& Stock::topval(const Stock& s) const { if(s.total_val > total_val) return s; else return ?????; } [1] s.total_val是 作为参数的传递对象 的总值;total_val是 用来调用该方法的对象 的总值 [2] 问题在于如何称呼 ?????
【2】解决方法 及 知识引入:
this指针:指向用来调用成员函数的对象 所有的类方法都将this指针设置为 调用它的对象 的地址
eg: 这样的话,stock1.topval ( stock2 ); 中,将this设置为stock1对象的地址,使得这个指针可用于 topval() 方法 函数中的 total_val 也只是 this->total_val 的简写
每个成员函数(包括构造函数与析构函数)都有一个this指针,指向调用对象 (1)要引用整个调用对象:使用表达式 *this (2)函数后面的const将this限定成const,这样的话就不能使用this来修改对象的值
C++ 1 2 3 4 5const Stock& Stock::topval(const Stock& s) const { if(s.total_val > total_val) return s; else return *this*; }
6)对象数组¶
方案: (1)使用默认构造函数创建数组元素 (2)花括号内的构造函数将创建临时对象【数组对象的构造函数将包括对每个元素单独调用的构造函数】 (3)将临时对象中的内容复制到相应的元素中 因此想要创建 类对象数组 ,则这个类必须要有默认构造函数
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
7)类作用域¶
【1】在类中定义的名称(类数据成员名 and 类成员函数名)的作用域为整个类,它们只在该类中是已知的,在类外是不可知的
【2】类作用域意味着不能从外部直接访问类的成员。要想调用公有成员函数,必须通过对象
C++ | |
---|---|
1 2 3 |
|
【3】定义成员函数时,必须使用类作用域解析运算符
C++ | |
---|---|
1 2 3 4 |
|
【4】在类声明or成员函数定义中,可以使用未修饰的成员名称 [ 因为名称的作用域在类内,这几个一家亲 ]
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
【5】如何在类中创建一个由所有对象共享的常量? (1)问题引入:
C++ | |
---|---|
1 2 3 4 5 6 7 |
|
(2)问题解决:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 |
|
8)抽象数据类型(ADT)¶
以stack作例子:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 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;
}
¶
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
|