当前位置: 首页 > news >正文

[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章:隐藏实现

访问控制:publicprivatefriend
将嵌套结构声明为友元 可以将嵌套结构声明为友元,使其能够访问外部类的私有成员。
对象布局
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(主要目标)

数据抽象的主要目标:
  1. 隐藏复杂性:将复杂的实现细节隐藏起来,只暴露简单的接口。

  2. 提供简洁的接口:通过封装数据和行为,提供易于使用的接口。

  3. 增强代码复用性:通过封装和抽象,创建通用的类库,便于在不同项目中复用。

  4. 提高代码安全性:隐藏内部实现细节,防止外部代码直接访问或修改内部数据,避免数据被滥用。

  5. 便于维护和扩展:由于内部实现对外隐藏,修改内部实现不会影响到使用该类的代码,只要接口保持不变。


Object = Characteristic + Behavior(对象 = 特征 + 行为)

对象的定义:
  • 对象(Object) 是面向对象编程中的基本单位,它封装了数据和操作这些数据的方法。

  • 特征(Characteristic):对象的状态,通常用成员变量(属性)表示。

  • 行为(Behavior):对象的行为,通常用成员函数(方法)表示。

 page 3 动态数组

dynamic array 动态数组
storage 存储
size 大小
next 下一个
quantity 数量
functionality: add, fetch, inflate 功能:添加,获取,膨胀

1. 库的设计原则
  • 封装性:将动态数组的实现细节隐藏起来,只暴露必要的接口(如addfetch)。

  • 接口设计:提供简洁的接口,让用户可以方便地使用动态数组,而不需要了解内部实现。

  • 模块化:将动态数组的功能划分为不同的模块(如初始化、添加元素add、获取元素fetch、扩展容量inflate等)。

2. 动态数组的实现思路
  • 动态内存管理:使用mallocreallocfree等函数动态分配和管理内存。

  • 扩展性:当数组容量不足时,通过inflate函数扩展存储容量。

  • 用户友好性:提供简单的接口(如addfetch),让用户可以方便地操作动态数组。

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

这主要是因为:

  1. 历史原因

    • typedef struct 是C语言中的传统用法,C语言中必须使用 typedef 来简化结构体的声明。

    • C++继承了C语言的语法,但C++提供了更灵活的语法,C++允许直接使用结构体名

  2. 风格和习惯

    • 在C语言中,typedef struct 是常见的写法。

    • 在C++中,通常不使用 typedef,而是直接使用结构体名。这种方式更简洁,也更符合C++的语法风格。

这里的语法结构可以分解为以下几个部分:

  1. typedef 关键字:用于创建类型别名
  2. struct CStashTag:定义了一个结构体类型,其标签为 CStashTag
  3. { ... }:结构体成员的定义。
  4. CStash:通过 typedefCStash 成为了 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;
    }
}   
  1. 如果你有一个结构体变量(而不是指针),你可以使用点操作符 (.) 来访问其成员。例如,如果 stash 是一个 CStash 类型的变量,那么你可以通过 stash.quantity 来访问 quantity 成员。

  2. 如果你有一个指向结构体的指针,你应该使用箭头操作符 (->) 来访问其成员。在你的例子中,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++中的newdelete操作符来完成。

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
int* p = new int() //
cout << "Value: " << *p << endl;  // 输出 0
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)

堆碎片化是指堆内存被频繁分配和释放后,导致堆空间变得碎片化。
碎片化的堆内存可能导致以下问题:

  1. 内存分配失败:即使堆中仍有足够的总内存,但由于碎片化,可能无法找到足够大的连续空间来满足新的分配请求。

  2. 性能下降:频繁的内存分配和释放会增加管理堆的开销。

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);
    }
    
    


    文章转载自:

    http://p1FxFoug.gcqcs.cn
    http://3zdxSWEu.gcqcs.cn
    http://8GWkU3bO.gcqcs.cn
    http://Ap4H6AJS.gcqcs.cn
    http://T7gDVVHA.gcqcs.cn
    http://Mi9Zhhs8.gcqcs.cn
    http://XkoQ6LEp.gcqcs.cn
    http://yraGvB8v.gcqcs.cn
    http://p7GqcP1A.gcqcs.cn
    http://CFKpYcwW.gcqcs.cn
    http://PMBhiVPj.gcqcs.cn
    http://EzPd78KN.gcqcs.cn
    http://NcYk96tq.gcqcs.cn
    http://4yUQhmcR.gcqcs.cn
    http://d6Nr6XNH.gcqcs.cn
    http://hwpZHpJk.gcqcs.cn
    http://Pdf2HfVg.gcqcs.cn
    http://afQfA6Oy.gcqcs.cn
    http://KdGpqftB.gcqcs.cn
    http://8suFo2KT.gcqcs.cn
    http://q9xf9cK5.gcqcs.cn
    http://Bx0GRbgS.gcqcs.cn
    http://ZPOfXF4g.gcqcs.cn
    http://YbcAqlSO.gcqcs.cn
    http://CMc3GwO6.gcqcs.cn
    http://lhk9xLMx.gcqcs.cn
    http://VwWkzSxl.gcqcs.cn
    http://9Lkejft0.gcqcs.cn
    http://zKFVt4LT.gcqcs.cn
    http://2p3rHAx0.gcqcs.cn
    http://www.dtcms.com/a/28612.html

    相关文章:

  1. 跳格子游戏
  2. Sun-Panel:简洁且美观的导航首页开源项目!!
  3. LeetCode 2595.奇偶位数:位运算
  4. DeepSeek全系列全平台部署(可代部署)
  5. 2025鸿蒙开发面试题汇总——通俗易懂
  6. DeepSeek-R1本地部署简易教程
  7. Python数据结构实战:链表的构建与操作
  8. Nginx安装:源代码编译安装
  9. vmware虚拟机Ubuntu Desktop系统怎么和我的电脑相互复制文件、内容
  10. 硬件岗位是否适合你?
  11. Linux环境基础开发工具的使用(三)
  12. 用算术右移操作实现整型数的除法
  13. 【git】工作流实战:从本地仓库到远程仓库,git pull 与git rebase使用讲解,案例解析
  14. C++,设计模式,【工厂方法模式】
  15. Openssl之SM2加解密命令
  16. 【个人记录】openEuler安装K3S并配置为GPU节点
  17. python高效使用06_while_True和while_1哪个效率更高
  18. OpenCV形态学操作
  19. Windows 10事件查看器
  20. PINN求解一维burgers方程
  21. 【AB-01】 AUTOSAR Builder软件安装
  22. C++:从拷贝构造函数到深浅拷贝
  23. 如何修改Windows系统Ollama模型存储位置
  24. 第三章 组件(7)- 布局与Sections
  25. Java——面向对象编程
  26. 使用多态来替换条件语句
  27. 【嵌入式Linux应用开发基础】进程间通信(3):共享内存
  28. 遗传算法与深度学习实战系列,自动调优深度神经网络和机器学习的超参数
  29. 完美转发使用
  30. 现代任务调度系统架构深度解析——以TaskSchedulerController为核心的弹性任务管理方案