[NKU]C++理论课 cours 3 数据抽象(封装->隐藏实现的手段,隐藏->封装的重要目标)
page 1 数据抽象 隐藏实现
Chapter 4: Data Abstraction
Improvements of C++
Size of an object
Inclusion guard
Nested Structure
Chapter 5: Hiding the implementation
Access control: public, private, friends
Declaring a nested structure as friend
Object layout
The keyword class
Application of the access control
Hiding the implementation
第4章:数据抽象
C++ 的改进
对象的大小 sizeof
运算符可用于获取对象的大小。
包含防护(头文件保护). 为了避免头文件的重复包含,C++ 使用包含防护机制(如 #ifndef
、#define
、#endif
)。
嵌套结构 在 C++ 中,可以在一个结构体或类中定义另一个结构体或类,这种结构称为嵌套结构。嵌套结构可以访问外部结构的成员。
第5章:隐藏实现
访问控制:public
、private
、friend
将嵌套结构声明为友元 可以将嵌套结构声明为友元,使其能够访问外部类的私有成员。
对象布局
class
关键字 class
是 C++ 中用于定义类的关键字。与 struct
不同,默认情况下,class
的成员是私有的,而 struct
的成员是公有的。
访问控制的应用 通过合理使用访问控制,可以隐藏类的实现细节,只暴露必要的接口。这有助于提高代码的封装性和可维护性。
隐藏实现 隐藏实现是数据抽象的核心思想之一。通过将类的实现细节隐藏在私有成员中,只通过公共接口与外部交互,可以减少依赖关系,提高代码的灵活性和安全性。
-
封装 是一种更广泛的概念,指的是将数据和行为组合在一起,并通过访问控制隐藏数据。
-
隐藏实现 是封装的一个重要目标,更侧重于隐藏类的内部实现细节,只暴露必要的接口。
在实际编程中,封装是实现隐藏实现的手段,而隐藏实现是封装的一个重要目标。
通过封装,可以实现隐藏实现,从而提高代码的安全性、可维护性和复用性。
page 2
main objectives
Object = characteristic + behavior
C++ advantages against C
How to organize header files
what should be put into a header file
how to avoid multiple declaration
how to use the header file
主要目标
对象=特征+行为
c++相对于C的优势
如何组织头文件
头文件里应该放些什么
如何避免多次申报
如何使用头文件
Main Objectives(主要目标)
数据抽象的主要目标:
-
隐藏复杂性:将复杂的实现细节隐藏起来,只暴露简单的接口。
-
提供简洁的接口:通过封装数据和行为,提供易于使用的接口。
-
增强代码复用性:通过封装和抽象,创建通用的类库,便于在不同项目中复用。
-
提高代码安全性:隐藏内部实现细节,防止外部代码直接访问或修改内部数据,避免数据被滥用。
-
便于维护和扩展:由于内部实现对外隐藏,修改内部实现不会影响到使用该类的代码,只要接口保持不变。
Object = Characteristic + Behavior(对象 = 特征 + 行为)
对象的定义:
-
对象(Object) 是面向对象编程中的基本单位,它封装了数据和操作这些数据的方法。
-
特征(Characteristic):对象的状态,通常用成员变量(属性)表示。
-
行为(Behavior):对象的行为,通常用成员函数(方法)表示。
page 3 动态数组
dynamic array 动态数组
storage 存储
size 大小
next 下一个
quantity 数量
functionality: add, fetch, inflate 功能:添加,获取,膨胀
1. 库的设计原则
-
封装性:将动态数组的实现细节隐藏起来,只暴露必要的接口(如
add
、fetch
)。 -
接口设计:提供简洁的接口,让用户可以方便地使用动态数组,而不需要了解内部实现。
-
模块化:将动态数组的功能划分为不同的模块(如初始化、添加元素add、获取元素fetch、扩展容量inflate等)。
2. 动态数组的实现思路
-
动态内存管理:使用
malloc
、realloc
和free
等函数动态分配和管理内存。 -
扩展性:当数组容量不足时,通过
inflate
函数扩展存储容量。 -
用户友好性:提供简单的接口(如
add
、fetch
),让用户可以方便地操作动态数组。
malloc
-
全称:Memory allocation(内存分配)
-
功能:分配一块指定大小的内存,并返回指向这块内存的指针。
-
原型:
c复制
void* malloc(size_t size);
-
说明:
-
size
是需要分配的内存大小(以字节为单位)。 -
返回值是一个
void*
类型的指针,指向分配的内存块的起始位置。 -
如果分配失败,返回
NULL
。 -
分配的内存内容是未初始化的,可能包含任意值。
-
2. realloc
-
全称:Resize allocation(重新分配内存)
-
功能:调整已分配内存块的大小。如果需要更大的内存块,可能会移动内存块的位置。
-
原型:
c复制
void* realloc(void* ptr, size_t new_size);
page4-9 动态数组
CLib.h
//CLib.h
typedef struct CStashTag {
int size; // Size of each space
int quantity; // Number of storage spaces
int next; // Next empty space
// Dynamically allocated array of bytes:
unsigned char* storage;
} CStash; // a place to hide something.
// } CStash;:在C语言中,CStash 是结构体的标签,为结构体定义了一个新的类型名。
void initialize (CStash* s, int size );
void cleanup (CStash* s );
int add (CStash* s, const void* element);
void* fetch (CStash* s, int index );
int count (CStash* s );
void inflate (CStash* s, int increase );
为什么 CStash
使用了 typedef
?
这主要是因为:
-
历史原因:
-
typedef struct
是C语言中的传统用法,C语言中必须使用typedef
来简化结构体的声明。 -
C++继承了C语言的语法,但C++提供了更灵活的语法,C++允许直接使用结构体名。
-
-
风格和习惯:
-
在C语言中,
typedef struct
是常见的写法。 -
在C++中,通常不使用
typedef
,而是直接使用结构体名。这种方式更简洁,也更符合C++的语法风格。
-
这里的语法结构可以分解为以下几个部分:
typedef
关键字:用于创建类型别名。struct CStashTag
:定义了一个结构体类型,其标签为CStashTag
。{ ... }
:结构体成员的定义。CStash
:通过typedef
,CStash
成为了struct CStashTag
的别名。
因此,在定义了这个结构体之后,你可以直接使用 CStash
来声明该类型的变量,而不需要再使用 struct CStashTag
。例如:
CLib.CPP
//CLib.CPP
#include "CLib.h"
#include <iostream>
#include <cassert>
using namespace std;
// Quantity of elements to add when increasing storage
const int increment = 100;
void initialize(CStash* s, int sz) {
s->size = sz; // 设置每个存储单元的大小
s->quantity = 0; // 初始化存储容量为 0(表示没有分配内存)
s->storage = 0; // 初始化存储指针为 NULL(表示没有分配内存)
s->next = 0; // 初始化下一个可用位置为 0
} // // 初始化intStash initialize(&intStash, sizeof(int));
int add(CStash* s, const void* element) {
if(s->next >= s->quantity) //Enough space left?
inflate(s, increment);
// Copy element into storage,
// starting at next empty space:
int startBytes = s->next * s->size;
unsigned char* e = (unsigned char*)element;
for(int i = 0; i < s->size; i++)
s->storage[startBytes + i] = e[i];
s->next++;
return(s->next - 1); // Index number
} // to be continued
void* fetch(CStash* s, int index) {
// Check index boundaries:
assert(0 <= index);
if(index >= s->next)
return 0; // To indicate the end
// Produce pointer to desired element:
return &(s->storage[index * s->size]);
}
int count(CStash* s) {
return s->next; // Elements in CStash
} // to be continued
void inflate(CStash* s, int increase) {
assert(increase > 0);
int newQuantity = s->quantity + increase;
int newBytes = newQuantity * s->size;
int oldBytes = s->quantity * s->size;
unsigned char* b = new unsigned char[newBytes];
for(int i = 0; i < oldBytes; i++)
b[i] = s->storage[ i ]; // Copy old to new
delete [ ](s->storage); // Old storage
s->storage = b; // Point to new memory
s->quantity = newQuantity;
} // to be continued
void cleanup(CStash* s) {
if(s->storage != 0) {
cout << "freeing storage" << endl;
delete [ ]s->storage;
}
}
-
如果你有一个结构体变量(而不是指针),你可以使用点操作符 (
.
) 来访问其成员。例如,如果stash
是一个CStash
类型的变量,那么你可以通过stash.quantity
来访问quantity
成员。 -
如果你有一个指向结构体的指针,你应该使用箭头操作符 (
->
) 来访问其成员。在你的例子中,s
是一个指向CStash
的指针,所以s->quantity
是正确的访问方式。
举例如下
(.)操作符访问结构体成员#include <stdio.h> typedef struct { int quantity; } CStash; int main() { CStash stash; // 创建一个结构体变量 stash.quantity = 5; // 使用点操作符访问并修改成员 printf("stash.quantity = %d\n", stash.quantity); // 输出成员的值 return 0; }
(->)操作符访问结构体成员
#include <stdio.h> typedef struct { int quantity; } CStash; int main() { CStash stashes[2]; // 创建一个结构体数组 stashes[0].quantity = 10; // 直接访问并修改数组第一个元素的成员 stashes[1].quantity = 20; // 直接访问并修改数组第二个元素的成员 // 创建一个指向结构体数组第一个元素的指针 CStash *s = &stashes[0]; // 使用箭头操作符访问并打印指针指向的结构体的成员 printf("s->quantity = %d\n", s->quantity); // 输出第一个元素的 quantity 值 // 移动指针到数组的第二个元素,并打印其成员 s = &stashes[1]; printf("s->quantity = %d\n", s->quantity); // 输出第二个元素的 quantity 值 return 0; }
page 10
Heap
new Type [ number_of_elements ]
return a pointer to the Type.
delete [ ] myArray
int *p = new int [100];
delete [ ] p;
memory leak, fragmented heap
堆
new Type [number_of_elements]
返回一个指向类型的指针。
删除[] myArray
Int *p = new Int [100];
删除[]p;
内存泄漏,碎片堆
1. 堆(Heap)
堆是程序运行时用于动态内存分配的内存区域。
与栈(Stack)不同,堆中的内存分配和释放是手动管理的,通常通过C++中的new
和delete
操作符来完成。
2. 动态数组的分配与释放
new Type[number_of_elements]
-
功能:在堆上分配一块连续的内存,用于存储一个动态数组。
-
返回值:返回一个指向数组首元素的指针。 int *p = new int [100];
-
示例:
int* p = new int[100]; // 分配一个包含100个int的动态数组
delete[] myArray
-
功能:释放通过
new[]
分配的动态数组。delete[] p; // 释放动态数组 -
注意:必须使用
delete[]
来释放new[]
分配的内存,而不是delete
。
否则,可能会导致未定义行为。 -
示例:
1 举例 new delete
int* p = new int; //
cout << "Value: " << *p << endl; // 输出未定义值,可能每次运行程序时都不一样。例如:Value: -12345678
或者:Value: 0
cout << "Value: " << *p << endl; // 输出 0
int* p = new int() //
int* p = new int(42); // new
操作符的返回值类型与分配的内存类型一致
delete p;
#include <iostream>
using namespace std;
int main() {
// 使用 new 分配单个 int
int* p = new int(42); // 初始化为 42
// 使用对象
cout << "Value: " << *p << endl;
// 使用 delete 释放单个对象
delete p;
return 0;
}
2 举例 new delete[]
int* arr = new int[5]; //数组中的值是未定义的,可能包含任意值。
arr[0]: -12345678 arr[1]: 42 arr[2]: 0 arr[3]: 314159 arr[4]: -1
int* arr = new int[5](); // 00000
int* arr = new int[5]{1,2,3,4,5};
delete[] arr;
#include <iostream>
using namespace std;
int main() {
// 使用 new[] 分配一个包含 5 个 int 的数组
int* arr = new int[5]{1, 2, 3, 4, 5}; // 初始化数组
// 使用数组
for (int i = 0; i < 5; i++) {
cout << "arr[" << i << "]: " << arr[i] << endl;
}
// 使用 delete[] 释放数组
delete[] arr;
return 0;
}
内存泄漏(Memory Leak)
内存泄漏是指程序分配了动态内存,但在使用完毕后没有正确释放,导致内存无法被其他程序或系统回收。
内存泄漏会导致程序占用的内存不断增加,最终可能导致程序崩溃或系统资源耗尽。
int* p = new int[100];
// 忘记释放内存
// delete[] p; // 如果忘记这一步,就会导致内存泄漏
堆碎片化(Fragmented Heap)
堆碎片化是指堆内存被频繁分配和释放后,导致堆空间变得碎片化。
碎片化的堆内存可能导致以下问题:
-
内存分配失败:即使堆中仍有足够的总内存,但由于碎片化,可能无法找到足够大的连续空间来满足新的分配请求。
-
性能下降:频繁的内存分配和释放会增加管理堆的开销。
void example() {
for (int i = 0; i < 1000; i++) {
int* p = new int[100];
delete[] p; // 正确释放内存
}
} //在这个例子中,虽然每次分配的内存都被正确释放,但频繁的分配和释放可能导致堆碎片化。
正确使用new[] delete[]避免 内存泄漏和堆碎片化
#include <iostream>
using namespace std;
int main() {
// 动态分配一个包含100个int的数组
int* p = new int[100];
// 使用数组
for (int i = 0; i < 100; i++) {
p[i] = i * i; // 初始化数组
}
// 输出数组内容
for (int i = 0; i < 100; i++) {
cout << "p[" << i << "]: " << p[i] << endl;
}
// 释放动态数组
delete[] p;
return 0;
}
page 11-13
CLabTest.cpp
#include "CLib.h" //CLib.h:假设这是一个自定义的头文件,定义了 CStash 结构体以及相关的函数(initialize、add、fetch、cleanup 等)。
#include <fstream> //文件
#include <iostream> //控制台
#include <string>//字符
#include <cassert> //assert
using namespace std;
int main( ) {
// Define variables at the beginning
// of the block, as in C:
CStash intStash, stringStash; //定义对象用于存储 int string
int i; //循环计算
char* cp; // 字符指针,提取字符串
ifstream in; //打开文件读取,输入流对象
string line; // 字符串变量,读取文件美航
const int bufsize = 80; 定义常量 整形 字符串最大长度80
// to be continued
// 初始化intStash
initialize(&intStash, sizeof(int));
for(i = 0; i < 100; i++)
add(&intStash, &i);
for(i = 0; i < count(&intStash); i++)
cout << "fetch(&intStash, " << i << ") = "
<< *(int*)fetch(&intStash, i)
<< endl;
// to be continued
// Holds 80-character strings:
initialize(&stringStash, sizeof(char)*bufsize);
in.open("CLibTest.cpp");
assert(in);
while(getline(in, line))
add(&stringStash, line.c_str());
i = 0;
while( (cp = (char*)fetch(&stringStash,i++) )!=0)
cout << "fetch(&stringStash, " << i << ") = "
<< cp << endl;
cleanup(&intStash);
cleanup(&stringStash);
}
完整代码
//CLib.h
typedef struct CStashTag {
int size; // Size of each space
int quantity; // Number of storage spaces
int next; // Next empty space
// Dynamically allocated array of bytes:
unsigned char* storage;
} CStash; // a place to hide something.
// } CStash;:在C语言中,CStash 是结构体的标签,为结构体定义了一个新的类型名。
void initialize (CStash* s, int size );
void cleanup (CStash* s );
int add (CStash* s, const void* element);
void* fetch (CStash* s, int index );
int count (CStash* s );
void inflate (CStash* s, int increase );
//CLib.CPP
#include "CLib.h"
#include <iostream>
#include <cassert>
using namespace std;
// Quantity of elements to add when increasing storage
const int increment = 100;
//初始化
void initialize(CStash* s, int sz) { // CStash* s表示需要一个指向 CStash 类型结构体的指针
s->size = sz; // 设置每个存储单元的大小
s->quantity = 0; // 初始化存储容量为 0(表示没有分配内存)
s->storage = 0; // 初始化存储指针为 NULL(表示没有分配内存)
s->next = 0; // 初始化下一个可用位置为 0
} // // 初始化intStash initialize(&intStash, sizeof(int));
//相加
int add(CStash* s, const void* element) {
if(s->next >= s->quantity) // Enough space left?
inflate(s, increment);
// Copy element into storage, 将element拷贝到storage
// starting at next empty space: 在下一个empty空间开始
int startBytes = s->next * s->size;
unsigned char* e = (unsigned char*)element;
for(int i = 0; i < s->size; i++)
s->storage[startBytes + i] = e[i];
s->next++;
return(s->next - 1); // Index number
} // add(&intStash, &i);
void* fetch(CStash* s, int index) {
// Check index boundaries:
assert(0 <= index);
if(index >= s->next)
return 0; // To indicate the end
// Produce pointer to desired element:
return &(s->storage[index * s->size]);
}
int count(CStash* s) {
return s->next; // Elements in CStash
} // to be continued
void inflate(CStash* s, int increase) {
assert(increase > 0);
int newQuantity = s->quantity + increase;
int newBytes = newQuantity * s->size;
int oldBytes = s->quantity * s->size;
unsigned char* b = new unsigned char[newBytes];
for(int i = 0; i < oldBytes; i++)
b[i] = s->storage[ i ]; // Copy old to new
delete [ ](s->storage); // Old storage
s->storage = b; // Point to new memory
s->quantity = newQuantity;
} // inflate(s, increment);
void cleanup(CStash* s) {
if(s->storage != 0) {
cout << "freeing storage" << endl;
delete [ ]s->storage;
}
}
#include "CLib.h" //CLib.h:假设这是一个自定义的头文件,定义了 CStash 结构体以及相关的函数(initialize、add、fetch、cleanup 等)。
#include <fstream> //文件
#include <iostream> //控制台
#include <string>//字符
#include <cassert> //assert
using namespace std;
int main( ) {
// Define variables at the beginning
// of the block, as in C:
CStash intStash, stringStash; //定义对象用于存储 int string
int i; //循环计算
char* cp; // 字符指针char point,提取字符串
ifstream in; //打开文件读取,输入流对象
string line; // 字符串变量,读取文件美航
const int bufsize = 80; 定义常量 整形 字符串最大长度80
// to be continued
// 初始化intStash
initialize(&intStash, sizeof(int)); // &intStash 获取intStash变量的地址
for(i = 0; i < 100; i++)
add(&intStash, &i);
for(i = 0; i < count(&intStash); i++)
cout << "fetch(&intStash, " << i << ") = "
<< *(int*)fetch(&intStash, i)
<< endl;
// to be continued
// Holds 80-character strings:
initialize(&stringStash, sizeof(char)*bufsize);
in.open("CLibTest.cpp");
assert(in);
while(getline(in, line))
add(&stringStash, line.c_str());
i = 0;
while( (cp = (char*)fetch(&stringStash,i++) )!=0)
cout << "fetch(&stringStash, " << i << ") = "
<< cp << endl;
cleanup(&intStash);
cleanup(&stringStash);
}