[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: 0cout << "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);
}
 
                