- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章C++动态数组类的封装实例由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
C++中的动态数组(Dynamic Array)是指动态分配的、可以根据需求动态增长占用内存的数组。为了实现一个动态数组类的封装,我们需要考虑几个问题:new/delete的使用、内存分配策略、类的四大函数(构造函数、拷贝构造函数、拷贝赋值运算符、析构函数)、运算符的重载。涉及到的知识点很多,对此本文只做简单的介绍.
1、内存分配策略 。
当用new为一个动态数组申请一块内存时,数组中的元素是连续存储的,例如 vector和string。当向一个动态数组添加元素时,如果没有空间容纳新元素,不可能简单地将新元素添加到内存中的其他位置——因为元素必须连续存储。所以必须重新分配一块更大的内存空间,将原来的元素从旧位置移动到新空间中,然后添加新元素,释放旧的内存空间。如果我们每添加一个新元素,就执行一次这样的内存分配和释放操作,效率将会慢到不行.
为了避免上述的代价,必须减少内存重新分配的次数。所以我们采取的策略是:在不得不分配新的内存空间时,分配比新的空间需求更大的内存空间(通常为2倍)。这样,在相当一段时间内,添加元素时就不用重新申请内存空间。注意,只有当迫不得已时才可以分配新的内存空间.
2、类的四大函数 。
一个C++类一般至少有四大函数,即构造函数、拷贝构造函数、拷贝赋值运算符、析构函数。如果类未自己定义上述函数,C++编译器将为其合成4个默认的版本。但是往往编译器合成的并不是我们所期望的,为此我们有必要自己定义它们.
1.构造函数 。
类的构造函数(constructor)用来初始化类对象的非static数据成员,无论何时只要类的对象被创建,就会执行构造函数.
1
2
3
4
5
6
|
class
Foo {
public
:
Foo();
// 构造函数
Foo(string &s);
// ...
};
|
构造函数的名字和类名相同,没有返回类型。类可以包含多个构造函数(重载),它们之间在参数数量或类型上需要有所区别。构造函数有一个初始化部分和一个函数体,成员的初始化是在函数体执行之前完成的.
2.拷贝构造函数 。
如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数(copy constructor).
1
2
3
4
5
6
|
class
Foo {
public
:
Foo();
Foo(
const
Foo&);
// 拷贝构造函数
// ...
};
|
拷贝构造函数定义了如何用一个对象初始化另一个同类型的对象。拷贝初始化通常使用拷贝构造函数来完成。拷贝初始化发生在下列情况中:
使用等号(=)初始化一个变量 将一个对象作为实参传递给一个非引用类型的形参 从一个返回类型为非引用类型的函数返回一个对象 用花括号列表初始化一个数组中的元素 。
3.拷贝赋值运算符 。
类的拷贝赋值运算符(copy-assignment operator)是一个名为operator=的函数。类似于其他任何函数,它也有一个返回类型和一个参数列表.
1
2
3
4
5
6
|
class
Foo {
public
:
Foo();
Foo& operator=(
const
Foo&);
// 赋值运算符
// ...
};
|
拷贝赋值运算符定义了如何将一个对象赋值给另一个同类型的对象。赋值运算符是一个成员函数也是一个二元运算符,其左侧运算对象就绑定到隐式的this指针,右侧运算对象作为显式参数传递。注意:为了与内置类型的赋值保持一致,赋值运算符通常返回一个指向其左侧运算对象的引用.
4.析构函数 。
类的析构函数(destructor)用来释放类对象使用的资源并销毁类对象的非static数据成员,无论何时只要一个对象被销毁,就会自动执行析构函数.
1
2
3
4
5
|
class
Foo {
public
:
~Foo();
// 析构函数
// ...
};
|
析构函数的名字由波浪号(~)加类名构成,也没有返回类型。由于析构函数不接受参数,因此它不能被重载。析构函数有一个函数体和一个析构部分,销毁一个对象时,首先执行析构函数体,然后按初始化顺序的逆序销毁成员.
3、运算符的重载 。
重载的运算符是具有特殊名字的函数:它们的名字由关键字operator和其后要定义的运算符号共同组成。和其他函数一样,重载的运算符也包含返回类型、参数列表、函数体,比如拷贝赋值运算符.
当我们定义重载的运算符时,必须首先决定是将其声明为类的成员函数还是声明为一个普通的非成员函数。有些运算符必须作为成员,而另一些运算符作为普通函数比作为成员更好:
赋值(=)、下标([ ])、调用(( ))和成员访问箭头(->)运算符必须是成员。 复合赋值运算符一般来说应该是成员,但并非必须,这一点与赋值运算符略有不同。 改变对象状态的运算符或者与给定类型密切相关的运算符,如递增、递减、解引用运算符,通常应该是成员。 具有对称性的运算符可能转换任意一端的运算对象,例如算术、相等性、关系和位运算符等,因此它们通常应该是普通的非成员函数。 当然,除了赋值运算符之外,我们还需要为动态数组定义下标运算符operator []。下标运算符必须是成员函数。为了让下标可以出现在赋值运算符的任意一端,下标运算符函数通常返回所访问元素的引用.
4、动态数组类的封装 。
下面给出了动态数组DArray类的接口:
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
|
class
DArray
{
private
:
double
*m_Data;
// 存放数组的动态内存指针
int
m_Size;
// 数组的元素个数
int
m_Max;
// 预留给动态数组的内存大小
private
:
void
Init();
// 初始化
void
Free();
// 释放动态内存
inline
bool
InvalidateIndex(
int
nIndex);
// 判断下标的合法性
public
:
DArray();
// 默认构造函数
DArray(
int
nSize,
double
dValue = 0);
// 构造函数,设置数组大小,默认值为dValue
DArray(
const
DArray& arr);
// 拷贝构造函数
DArray& operator=(
const
DArray& arr);
// 拷贝赋值运算符
~DArray();
// 析构函数
void
Print();
// 输出显式所有数组元素的值
int
GetSize();
// 获取数组的大小(元素个数)
void
SetSize(
int
nSize);
// 重新设置数组的大小,若nSize小于原大小,截断;否则,新元素置0
double
GetAt(
int
nIndex);
// 获取指定位置元素
void
SetAt(
int
nIndex,
double
dValue);
// 重置指定元素的值
void
PushBack(
double
dValue);
// 追加一个新元素到数组末尾
void
DeleteAt(
int
nIndex);
// 删除指定位置地元素
void
InsertAt(
int
nIndex,
double
dValue);
// 插入一个新的元素到数组中
double
operator[](
int
nIndex)
const
;
// 重载下标运算符[]
};
|
下面是实现方法:
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
|
void
DArray::Init()
{
m_Size = 0;
// 默认情况下数组不包含元素
m_Max = 1;
m_Data =
new
double
[m_Max];
}
void
DArray::Free()
{
delete
[] m_Data;
}
bool
DArray::InvalidateIndex(
int
nIndex)
{
if
(nIndex>=0 && nIndex<m_Size)
return
false
;
else
return
true
;
}
// 默认构造函数
DArray::DArray()
{
Init();
}
// 构造函数
DArray::DArray(
int
nSize,
double
dValue)
{
if
(nSize == 0)
Init();
else
{
m_Size = nSize;
m_Max = nSize;
m_Data =
new
double
[m_Max];
for
(
int
i=0; i<nSize; ++i)
m_Data[i]=dValue;
}
}
// 拷贝构造函数
DArray::DArray(
const
DArray& arr)
{
m_Size = arr.m_Size;
/*复制常规成员*/
m_Max = arr.m_Max;
m_Data =
new
double
[m_Max];
/*复制指针指向的内容*/
memcpy
(m_Data, arr.m_Data, m_Size*
sizeof
(
double
));
}
// 拷贝赋值运算符
DArray& DArray::operator=(
const
DArray& arr)
{
if
(
this
== &arr)
/*自赋值*/
return
*
this
;
m_Size = arr.m_Size;
m_Max = arr.m_Max;
/* 先将右侧对象拷贝到临时对象中,然后再销毁左侧对象*/
double
*m_Temp =
new
double
[m_Max];
memcpy
(m_Temp, arr.m_Data, m_Size*
sizeof
(
double
));
delete
[] m_Data;
m_Data = m_Temp;
return
*
this
;
}
// 析构函数
DArray::~DArray()
{
Free();
}
// 打印数组
void
DArray::Print()
{
if
(m_Size == 0)
{
cout <<
"Error: The empty array can't be Printed."
<< endl;
exit
(0);
}
else
{
for
(
int
i=0; i<m_Size; ++i)
cout << m_Data[i] <<
" "
;
cout << endl;
}
}
// 获取数组大小
int
DArray::GetSize()
{
return
m_Size;
}
// 重置数组大小
void
DArray::SetSize(
int
nSize)
{
if
(nSize < m_Size)
/*截断*/
{
for
(
int
i=nSize; i<m_Size; ++i)
m_Data[i] = 0;
}
if
(m_Size<=nSize && nSize<=m_Max)
/*新增元素置0*/
{
for
(
int
i=m_Size; i<nSize; ++i)
m_Data[i] = 0;
}
if
(nSize > m_Max)
/*需要重新分配空间*/
{
m_Max = nSize;
double
*temp =
new
double
[m_Max];
memcpy
(temp, m_Data, m_Size*
sizeof
(
double
));
for
(
int
i=m_Size; i<nSize; ++i)
temp[i] = 0;
delete
[] m_Data;
m_Data = temp;
}
m_Size = nSize;
/*设置数组大小*/
}
// 获取指定位置元素
double
DArray::GetAt(
int
nIndex)
{
if
(InvalidateIndex(nIndex))
{
cout <<
"Error: the index of GetAt is invalid!"
<< endl;
exit
(0);
}
return
m_Data[nIndex];
}
// 设置指定位置元素的值
void
DArray::SetAt(
int
nIndex,
double
dValue)
{
if
(InvalidateIndex(nIndex))
{
cout <<
"Error: the index of SetAt is invalid!"
<< endl;
exit
(0);
}
else
{
m_Data[nIndex] = dValue;
}
}
// 追加一个新元素到数组末尾
void
DArray::PushBack(
double
dValue)
{
if
(m_Size < m_Max)
{
m_Data[m_Size] = dValue;
}
else
{
m_Max = m_Max*2;
double
* temp =
new
double
[m_Max];
memcpy
(temp, m_Data, m_Size*
sizeof
(
double
));
delete
[] m_Data;
m_Data = temp;
m_Data[m_Size] = dValue;
}
++m_Size;
/*数组大小加1*/
}
// 从数组中删除一个元素
void
DArray::DeleteAt(
int
nIndex)
{
if
(InvalidateIndex(nIndex))
{
cout <<
"Error: the index of DeleteAt is invalid."
<< endl;
exit
(0);
}
else
{
for
(
int
i=nIndex; i<m_Size; ++i)
m_Data[i] = m_Data[i+1];
m_Data[m_Size-1] = 0;
--m_Size;
}
}
// 插入一个新元素到指定位置
void
DArray::InsertAt(
int
nIndex,
double
dValue)
{
if
(nIndex<0 || nIndex>m_Size)
{
cout <<
"Error: the index of InsertAt is invalid!"
<< endl;
exit
(0);
}
if
(m_Size < m_Max)
/* 未满,插入 */
{
for
(
int
i=m_Size-1; i>=nIndex; --i)
m_Data[i+1] = m_Data[i];
m_Data[nIndex] = dValue;
}
else
/* 重新分配空间 */
{
m_Max = m_Max*2;
double
* temp =
new
double
[m_Max];
memcpy
(temp, m_Data, m_Size*
sizeof
(
double
));
delete
[] m_Data;
m_Data = temp;
for
(
int
i=m_Size-1; i>=nIndex; --i)
m_Data[i+1] = m_Data[i];
m_Data[nIndex] = dValue;
}
++m_Size;
/* 数组大小加1 */
}
// 重载下标运算符[]
double
DArray::operator[](
int
nIndex)
const
{
if
(nIndex<0 || nIndex>=m_Size)
{
cout <<
"Error: the index in [] is invalid!"
<< endl;
exit
(0);
}
return
m_Data[nIndex];
}
|
经过简单的测试,暂时还没有发现Bug。可能测试并不全面,感兴趣的读者可以进一步测试并完善该程序.
附:String类的实现 。
C++ 的一个常见面试题是让你实现一个 String 类,限于时间,不可能要求具备 std::string 的功能,但至少要求能正确管理资源.
如果你弄懂了上面DArray类的写法,那么实现String类应该就不难了。因为面试官一般只是想考查你能不能正确地写出构造函数、析构函数、拷贝构造函数、拷贝赋值运算符以及+、[ ]、<<、>>运算符重载等等。下面给出一个String类的接口,你可以自己试试手实现一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class
String{
friend
ostream& operator<< (ostream&,String&);
//重载<<运算符
friend
istream& operator>> (istream&,String&);
//重载>>运算符
public
:
String();
// 默认构造函数
String(
const
char
* str);
// 带参构造函数
String(
const
String& rhs);
// 拷贝构造函数
String& operator=(
const
String& rhs);
// 拷贝赋值运算符
String operator+(
const
String& rhs)
const
;
//operator+
bool
operator==(
const
String&);
//operator==
bool
operator!=(
const
String&);
//operator!=
char
& operator[](unsigned
int
);
//operator[]
size_t
size()
const
;
const
char
* c_str()
const
;
~String();
// 析构函数
private
:
char
*m_data;
// 用于保存字符串
};
|
本文所述DArray类和String类的源码及测试代码可点击此处本站下载.
最后此篇关于C++动态数组类的封装实例的文章就讲到这里了,如果你想了解更多关于C++动态数组类的封装实例的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我需要使用封装打印3个变量,并且无法修改主类。 主类声明变量[汽缸、制造商、所有者],并希望使用如下参数创建和打印一个对象: System.out.println (new Vehicle (cyli
我正在研究“Beginning Ruby”这本书,在我进行封装之前一切都进行得很顺利。我明白这段代码在做什么,我只是不知道为什么要用这些方法设置它。 对于那些感兴趣的人 this is the lin
所以我一直在研究面向对象的完整开发 JavaScript 实践,并对以下示例感到好奇。 据我了解,(这对我来说很有意义)以下“ secret ”字段是“私有(private)”的: var MyObj
在面向对象程式设计方法中,封装(英语:Encapsulation)是指,一种将抽象性函式接口的实作细节部份包装、隐藏起来的方法。 封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代
封装 被定义为 把一个或多个项目封闭在一个物理的或者逻辑的包中 。在面向对象程序设计方法论中,封装是为了防止对实现细节的访问。 抽象和封装是面向对象程序设计的相关特性。抽象允许相关信息可
今天我读了一本书,作者写道,在一个设计良好的类中,访问属性的唯一方法是通过其中一个类方法。这是一个被广泛接受的想法吗?为什么封装属性如此重要?不做会有什么后果?我早些时候在某处读到这可以提高安全性或类
今天我接受了软件工程职位的面试。我已经从 stackoverflow 中阅读了很多关于面试的内容。他们向我询问了与 OOP 相关的正常情况。但他们也问我这些: 没有继承就可以封装吗? 没有继承就可以抽
我正在制作一个简单的网站,并编写了gradle构建脚本,该脚本将执行诸如lint并最小化css / html / js之类的事情。 在这一阶段,我刚刚完成了正在运行的CSS,但是只是初始的非结构化格式
下面的幻灯片是指 C++ 语言,它说 "encapsulation is about ensuring the user uses our ADT in a safe way" 但是,如果他可以访问我
尝试设置一个 Person 类 我在尝试将信息封装在类中以使其不会被意外更改时遇到了问题。除了当我尝试使用 setter/getters 进行封装时,该类工作得非常好。我认为问题是这些方法最终会互相循
面向对象的概念:封装、数据抽象和数据隐藏是3个不同的概念,但彼此之间非常相关。所以我很难通过阅读互联网上的信息来完全理解这些概念。一个地方可用的信息与互联网上另一个地方的信息相矛盾。有人可以指导我阅读
我被封装困住了。在我看来,好像我已经按照规则做了一切,但仍然不起作用。虽然我知道我错过了一些东西。我正在使用 Eclipse。 我想要通过创建一副标准 52 张卡片来进行一些练习,并尝试我学到的新东西
在java中,要获取映射中的所有键,我们可以使用方法keySet。但我想知道为什么方法名称不只是 keys ?名称 Set 是否会泄露有关实现的详细信息? 据我了解,Java 是静态类型语言,名称中包
我正在尝试找出如何防止应用程序中的页面变量被全局定义。我想出了一些方法,但我想知道是否有人们使用的通用标准方法。 我使用这种方法得到了我的插件设计模式:http://www.virgentech.co
我有一个 tcp 连接,我想在服务器类的 HandleConnectionAsync 方法中保持打开状态。它将从客户端接收持续更新。 private async void HandleConnecti
这是我类(class)的一个小样本: #include #include using std::string; using std::vector; struct Book { string
我想要类似下面的代码: class Foo{ public: void update() { for( auto dataModifier : _dataModifierLis
这已经困扰我一段时间了,所以我问了一个同事他是否能理解它,现在我来了;) 为什么可以在依赖属性的 PropertyChangedCallback 中访问持有类的私有(private)成员? 让我通过这
我知道封装意味着一个对象不应该直接访问不同对象的成员……但我猜它与公共(public)领域有关?我假设公共(public)方法不会破坏封装..?我只是在这里不清楚,很高兴得到任何解释。 最佳答案
我就废话不多说了,大家还是直接看代码吧~ ? 1
我是一名优秀的程序员,十分优秀!