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

[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, 当前已使用的存储单元数量, 调用 add 函数时,s->next 会递增,指向下一个空闲位置。
    }  // // 初始化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); // &intStash 获取了 intStash 的地址,并将其传递给 initialize 函数。 initialize 函数的参数是 CStash* s,即一个指向 CStash 类型的指针。因此,&intStash 是一个指向 CStash 的指针,而不是引用。
    
      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);
    }
    
    

    代码功能概述

    这段代码实现了一个简单的动态内存管理工具 CStash,用于存储不同类型的数据(如 intstring)。它提供了以下功能:

    1. 初始化:通过 initialize 函数设置存储单元的大小和初始状态。

    2. 添加数据:通过 add 函数将数据逐字节存储到动态分配的内存中。

    3. 获取数据:通过 fetch 函数根据索引获取存储的数据。

    4. 扩展内存:通过 inflate 函数动态扩展存储空间。

    5. 清理内存:通过 cleanup 函数释放动态分配的内存。

    main() 函数的功能

    main() 函数演示了如何使用 CStash 存储两种类型的数据:

    1. 存储 int 类型数据

      • 初始化一个 CStash 对象 intStash,每个存储单元的大小为 sizeof(int)

      • intStash 中添加 100 个整数。

      • 遍历并打印所有存储的整数。

    2. 存储 string 类型数据

      • 初始化另一个 CStash 对象 stringStash,每个存储单元的大小为 sizeof(char) * bufsize(80 字节)。

      • 从文件 CLibTest.cpp 中逐行读取字符串,并将其存储到 stringStash 中。

      • 遍历并打印所有存储的字符串。

    最后,使用 cleanup 函数释放动态分配的内存。

    输出内容

    假设文件 CLibTest.cpp 中有以下内容:

    复制

    Line 1
    Line 2
    Line 3

    程序的输出将是:

    复制

    fetch(&intStash, 0) = 0
    fetch(&intStash, 1) = 1
    fetch(&intStash, 2) = 2
    ...
    fetch(&intStash, 99) = 99
    
    fetch(&stringStash, 0) = Line 1
    fetch(&stringStash, 1) = Line 2
    fetch(&stringStash, 2) = Line 3

    总结

    这段代码实现了一个通用的动态内存管理工具 CStash,用于存储不同类型的数据。main() 函数演示了如何使用 CStash 存储 intstring 类型的数据,并从文件中读取字符串。程序的输出是存储的整数和字符串内容。

    逐元素说明

    unsigned char*

    unsigned char*
    是一个指向 unsigned char 类型的指针。
    为了更好地理解它的含义和用途,我们需要先了解 unsigned char 和指针的基本概念。

    1. unsigned char

    • unsigned char 是一种基本数据类型,表示一个无符号字符

    • 它通常占用 1字节(8位)的内存空间。

    • 由于它是无符号的,其取值范围是 0 到 255(即 0x000xFF)。

    • unsigned char 常用于表示单个字节的数据,尤其是在需要处理二进制数据或内存操作时。


    2. 指针

    • 指针 是一个变量,用于存储另一个变量的内存地址。

    • 指针的类型决定了它指向的数据类型。例如:

      • int* 指向 int 类型的数据。

      • char* 指向 char 类型的数据。

      • unsigned char* 指向 unsigned char 类型的数据。


    3. unsigned char*

    • unsigned char* 是一个指向 unsigned char 的指针,用于逐字节访问内存。

    • 它可以用来逐字节操作数据,而不需要关心数据的具体类型。这使得它非常适合处理二进制数据、内存拷贝或低级内存操作。

    逐字节访问数据

    unsigned char data[] = {0x12, 0x34, 0x56, 0x78};
    unsigned char* ptr = data;
    
    for (int i = 0; i < 4; i++) {
        cout << hex << (int)ptr[i] << " ";  // 输出:12 34 56 78
    }

    内存拷贝:

    int value = 0x12345678;  // 定义一个32(4x8)位整数
    unsigned char* src = (unsigned char*)&value;  // 将 value 的地址转换为 unsigned char* 指针
    unsigned char* dst = new unsigned char[4];    // 分配4字节的动态内存,一个字节8位,4x8=32
    
    for (int i = 0; i < 4; i++) {
        dst[i] = src[i];  // 逐字节复制
    }
    
    delete[] dst;  // 释放动态分配的内存
    逐字节访问
    • unsigned char* src = (unsigned char*)&value;value 的地址转换为 unsigned char* 指针,从而可以逐字节访问 value 的内存。

    • unsigned char* dst = new unsigned char[4];:分配4字节的动态内存,用于存储复制的数据。

    • dst[i] = src[i];:逐字节将 value 的内容复制到 dst 中。

    16位一个字节,8位半个字节

    1. 十六进制数 0x12345678 的含义

    0x12345678 是一个十六进制数,表示的是一个 32(4x8)位的整数
    它由8个十六进制数字组成,每个十六进制数字占用 4位(半字节),因此:

    • 8个十六进制数字 = 32位 = 4字节。

    具体来说:

    • 0x12 表示十六进制的 12

    • 0x34 表示十六进制的 34

    • 0x56 表示十六进制的 56

    • 0x78 表示十六进制的 78

    2. 十六进制数的位数

    十六进制数的位数取决于它表示的数值大小。例如:

    • 0x12 是一个 2位 的十六进制数,表示一个字节(8位)。

    • 0x1234 是一个 4位 的十六进制数,表示两个字节(16位)。

    • 0x12345678 是一个 8位 的十六进制数,表示四个字节(32位)。

    因此,0x12345678 是一个 32位的整数,需要 4个字节 来存储。


    3. 内存布局

    在你的代码中,int value = 0x12345678;
    定义了一个32位的整数变量 value,它的值是 0x12345678
    这个值在内存中以字节的形式存储,具体布局取决于系统的字节序(大端或小端)。

    疑问

    unsigned char 取值范围是 0 到 255(16x16)(即 0x000xFF)?
    1字节8位8个二进制位,0000 0000

    • 0x12 表示二进制的 0001 0010十进制的 18。1×16^1+2×16^0=16+2=18

    • 0xFF 表示二进制的 1111 1111
      0xFF 是十六进制表示法中的一种写法

    • 0x00 表示二进制的 0000 0000,十进制的 0。

    • 0xFF 表示二进制的 1111 1111,十进制的 255。

    1. 十六进制表示法(Hexadecimal)

    十六进制是一种基于16的数制,用于表示数字。它使用16个符号:0-9A-F(10-15),其中:

    • 0-9 表示数字 0 到 9。

    • A 表示 10,B 表示 11,C 表示 12,D 表示 13,E 表示 14,F 表示 15。

    十六进制的表示方式

    在C和C++中,十六进制数通常以 0x0X 开头。例如:

    • 0x12 表示十六进制数 12

    • 0xFF 表示十六进制数 FF

    为什么用十六进制?

    十六进制在计算机编程中非常常用,因为它可以简洁地表示二进制数据
    每1个十六进制数字对应4个二进制位(bit),因此:

    • 1字节(8位,8个二进制位(bit))可以用2个十六进制数字表示

    例如:

    • 0x12 表示二进制的 0001 0010

    • 0xFF 表示二进制的 1111 1111


    2. unsigned char 的取值范围

    unsigned char 是一种无符号字符类型,通常占用 1字节(8位)的内存。
    由于它是无符号的,其取值范围是 0 到 255(16x16)

    为什么是 0 到 255?

    1字节有8位,每一位可以是 01。因此,1字节可以表示的数值范围是:

    • 最小值:0000 0000(二进制)= 0(十进制)

    • 最大值:1111 1111(二进制)= 255(十进制)

    用十六进制表示:

    • 0x00 表示二进制的 0000 0000,十进制的 0。

    • 0xFF 表示二进制的 1111 1111,十进制的 255。


    3. 为什么有 0xFF

    0xFF 是十六进制表示法中的一种写法,表示一个字节的所有位都被设置为 1。它在编程中非常常用,用于表示:

    • 全1掩码:在位操作中,0xFF 可以用来将一个字节的所有位都设置为 1

    • 最大值:在无符号字符类型中,0xFF 表示最大值 255。

    4. 总结

    • 0x:表示十六进制数的前缀

    • FF:表示十六进制的最大值,对应二进制的 1111 1111,十进制的 255。

    • unsigned char:无符号字符类型,取值范围是 0 到 255,可以用十六进制表示为 0x000xFF

    add说明
    int add(CStash* s, const void* element){...}

    int add(CStash* s, const void* element) {
        if (s->next >= s->quantity) {  // 检查是否有足够的空间
            inflate(s, increment);     // 如果空间不足,扩展存储空间
        }
    
        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);  // 返回当前元素的索引(表示刚刚存储的元素的索引)
    }
    int startBytes = s->next * s->size;
    • 用于确定新元素动态分配的内存中的存储位置

    • s->next:表示当前已使用的存储单元数量,同时也是下一个空闲存储单元的索引。

    • s->size:表示每个存储单元的大小(以字节为单位)。

    • startBytes:计算从动态分配的内存的起始位置下一个空闲存储单元的起始字节偏移量。

    具体逻辑

    假设:

    • s->next = 3,表示已经有3个元素被存储,下一个元素将存储在第4个位置。

    • s->size = 4,表示每个存储单元的大小为4字节(例如,存储 int 类型的数据)。

    那么:

    int startBytes = s->next * s->size;  // 3 * 4 = 12

    这意味着下一个元素的存储位置从动态分配的内存的第12字节开始。

    详细解释

    1. 计算起始字节偏移量

      • startBytes = s->next * s->size 计算从动态分配的内存的起始位置到下一个空闲存储单元的起始字节偏移量。

      • 例如,如果 s->next = 3s->size = 4,则 startBytes = 12,表示下一个元素的存储位置从第12字节开始。

    2. 逐字节复制数据

      • 使用 unsigned char* 指针逐字节element 的内容复制到动态分配的内存中。

      • s->storage[startBytes + i] = e[i];
        element 的第 i 个字节复制到 s->storagestartBytes + i 位置。

    3. 更新 s->next

      • s->next++ 表示下一个可用位置的索引加1,为下一个元素的存储做好准备。

    总结

    • int startBytes = s->next * s->size;

      • 计算从动态分配的内存的起始位置到下一个空闲存储单元的起始字节偏移量。

      • 用于确定新元素在动态分配的内存中的存储位置。

    假设当前存储的元素是int类型的1, int是4个字节(一个字节是8位)
    1. 十进制 1
    二进制 0000 0000 0000 0000 0000 0000 0000 0001
    十六进制 0x00 00 00 01

    在小端序中,内存布局为:

    • 地址 0:0x01(最低字节)

    • 地址 1:0x00

    • 地址 2:0x00

    • 地址 3:0x00(最高字节)

    2. elemente 的关系
    • element 是一个指向整数 1 的指针。

    • e 是将 element 转换为 unsigned char* 类型后的指针,用于逐字节访问数据。

    3. e[i] 的内容

    假设 element 指向整数 1,那么 e 逐字节访问的内容如下:

    • e[0]0x01(最低字节)

    • e[1]0x00

    • e[2]0x00

    • e[3]0x00(最高字节)

    内存复制过程

    假设 s->size = 4s->next = 0startBytes = 0

    • s->storage[0] = e[0]0x01

    • s->storage[1] = e[1]0x00

    • s->storage[2] = e[2]0x00

    • s->storage[3] = e[3]0x00

    总结

    • 如果当前存储的元素是整数 1e[i] 的内容如下:

      • e[0]0x01

      • e[1]0x00

      • e[2]0x00

      • e[3]0x00

    • 这些值将被逐字节复制到动态分配的内存中。

    • 这种逐字节操作使得 CStash 可以存储不同类型的数据,而不需要关心数据的具体类型。

    值得注意的是这里返回之后并没有打印

    for(i = 0; i < 100; i++){
        add(&intStash, &i); 
    }
    // 可以修改为
    for(i = 0; i < 100; i++){
        int index = add(&intStash, &i);
        cout<< "当前索引:" << index <<endl;
    }//这将输出每次添加元素的索引,帮助你验证 add 函数是否正确工作。

    修改后的输出结果

    当前索引:0
    当前索引:1
    当前索引:2
    ...
    当前索引:99
    fetch(&intStash, 0) = 0
    fetch(&intStash, 1) = 1
    fetch(&intStash, 2) = 2
    ...
    fetch(&intStash, 99) = 99


    fetch(&stringStash, 1) = Line 1
    fetch(&stringStash, 2) = Line 2
    fetch(&stringStash, 3) = Line 3

    inflate说明

    void inflate(CStash* s, int increase) {...}

    inflate 函数的作用是动态扩展 CStash 的存储空间。
    当当前存储空间不足时,这个函数会被调用,以分配更多的内存,并将旧数据复制到新的内存中。让我们逐步解析这个函数的逻辑。

    void inflate(CStash* s, int increase) {
        assert(increase > 0);  // 确保增加的数量大于 0 // const int increment = 100;
        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];
        }
    
        // 释放旧内存
        delete[] s->storage;
    
        // 更新存储指针和存储单元数量
        s->storage = b;
        s->quantity = newQuantity;
    }

    详细解释

    1. 参数
    • CStash* s:指向 CStash 结构体的指针,表示需要扩展存储空间的对象s

    • int increase:表示需要增加的存储单元数量

    2. 断言
    assert(increase > 0);
    • 作用:确保 increase 大于 0,避免无效的内存扩展操作。

    • 如果 increase <= 0,程序会触发断言失败并终止。

    3. 计算新的存储单元数量(旧的quantity+新的increase
    int newQuantity = s->quantity + increase;
    • s->quantity:当前已分配的存储单元数量

    • increase:需要增加的存储单元数量

    • newQuantity扩展后的总存储单元数量

    4. 计算新的总字节数((旧的quantity+新的increase)*4 )
    int newBytes = newQuantity * s->size;
    • s->size:每个存储单元的大小(以字节为单位)
      int是4个字节,->size 被设置为 sizeof(int)是4

    • newBytes:扩展后的总字节数。

    5. 计算旧的总字节数( 旧的quantity*4 )
    int oldBytes = s->quantity * s->size;
    • oldBytes:当前已分配的总字节数。

    6. 分配新的内存
    unsigned char* b = new unsigned char[newBytes];
    • 分配新的内存,大小为 newBytes 字节,并返回一个指向这块内存的指针 b

    • b 是指向新分配内存的指针。

    • unsigned char:无符号字符类型,通常占用 1字节 的内存空间。

    • newBytes:表示需要分配的总字节数。

    • new unsigned char[newBytes]:使用 new 运算符动态分配一块内存,大小为 newBytes 字节。

    • unsigned char* bb 是一个指向 unsigned char 的指针,用于存储分配的内存的地址。

    7. 复制旧数据到新内存
    for (int i = 0; i < oldBytes; i++) {
        b[i] = s->storage[i];
    }
    • 使用逐字节复制的方式,将旧内存中的数据复制到新内存中。

    • 这确保了旧数据不会丢失。

    8. 释放旧内存
    delete[] s->storage;
    • 释放旧的内存,避免内存泄漏。

    • s->storage 是指向旧内存的指针。

    9. 更新存储指针和存储单元数量
    s->storage = b;
    s->quantity = newQuantity;
    • 更新 CStash 的存储指针,指向新的内存。

    • 更新存储单元数量为新的数量。

    完整逻辑

    1. 计算新的存储单元数量(newQuantity = s->quantity+increase)和新的总字节数(newBytes = (s->quantity+increase)*4)。

    2. 分配新的内存 (unsigned char* b = new unsigned char[newBytes])。

    3. 将旧数据从旧内存复制到新内存
      unsigned char* b = new unsigned char[newBytes];
      for(int i = 0; i < oldBytes; i++) {
          b[i] = s->storage[ i ];  }

    4. 释放旧内存
      delete[] (s->storage);

    5. 更新存储指针(s->storage)和存储单元数量(s->quantity)。
          s->storage = b; // Point to new memory    
          s->quantity = newQuantity;

    打印展示
    声明int count(CStash* s){...}
    调用count(&intStash)
    调用fetch(&Stash)

    int count(CStash* s) {
        return s->next;  // Elements in CStash
    } // 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]); 
    }
    
    for(i = 0; i < count(&intStash); i++)
          cout << "fetch(&intStash, " << i << ") = "
               << *(int*)fetch(&intStash, i)
               << endl; 

    输出结果

    fetch(&intStash, 0) = 0
    fetch(&intStash, 1) = 1
    fetch(&intStash, 2) = 2
    ...
    fetch(&intStash, 99) = 99

    这段代码的作用是遍历 intStash存储的内容(所有整数),并通过 fetch 函数逐个获取这些整数的值,然后打印出来。

    fetch

    void* fetch(CStash* s, int index) {...} 返回指针

    fetch 函数的作用是根据给定的索引 index,从 CStash 的动态存储空间中获取指定元素的地址。让我们逐步解析这个函数的逻辑。

    void* fetch(CStash* s, int index) {
        // Check index boundaries:
        assert(0 <= index);  // 确保索引非负
        if (index >= s->next) // 检查索引 index 是否超出了当前已存储的元素范围。如果索引超出范围,返回 NULL
            return 0;  
        // Produce pointer to desired element:
        return &(s->storage[index * s->size]);
    }

    if (index >= s->next) 一定不会错,
    打印里面的 调用*(int*)fetch(&intStash, i),
    调用的声明里是void* fetch(CStash* s, int index)
    i就是 index , i(index)本身< count(&intStash),
    int count(CStash* s) {
        return s->next
    }
    count(&intStash) 也就是s->next,
    因此一定是index < s->next

    核心目的是
    检查索引 index 是否超出了当前已存储的元素范围 s->next

    1. void* fetch(CStash* s, int index) 的返回类型

    fetch 函数的返回类型是 void*,表示它返回一个指向 void 的指针。
    在这种情况下,return 0;return NULL; 是等价的。

    • 0:在C和C++中,0 可以隐式转换为任何指针类型,表示空指针(NULL

    • NULL:是一个宏定义,通常在C中定义为 ((void*)0),在C++中定义为 nullptr(C++11及以后)。

    因此,return 0;return NULL; 在这里的效果是相同的,都表示返回一个空指针。

    详细解释  &(s->storage[index * s->size])

    //调用*(int*)fetch(&intStash, i)
    
    //fetch定义
    void* fetch(CStash* s, int index) {
        // Check index boundaries:
        assert(0 <= index);  // 确保索引非负
        if (index >= s->next) // 检查索引 index 是否超出了当前已存储的元素范围。如果索引超出范围,返回 NULL
            return 0;  
        // Produce pointer to desired element:
        return &(s->storage[index * s->size]);
    }
    1. s->storage
    • s->storage:这是 CStash 结构体中的一个成员变量类型为 unsigned char*
      (无符号字符类型的指针),指向动态分配的内存。
      这块内存用于存储所有元素,每个元素占用 s->size 字节。

    2. index * s->size
    • index:要访问的元素的索引

    • s->size:每个存储单元的大小(以字节为单位)。

    • index * s->size:计算目标元素的起始字节偏移量。例如:

      • 如果 index = 2s->size = 4,则 index * s->size = 8
        表示目标元素从第 8 字节开始。

    3. s->storage[index * s->size]
    • s->storage[index * s->size]:访问动态分配的内存中,
      从起始位置偏移 index * s->size 字节的位置
      这表示目标元素的第一个字节

    4. &(s->storage[index * s->size])
    • &取地址操作符,用于获取变量或内存位置的地址。

    • &(s->storage[index * s->size]):获取目标元素的地址,并返回一个指向该地址的指针。

    完整逻辑

    这行代码的作用是:

    1. 计算目标元素的起始字节偏移量:index * s->size

    2. 访问动态分配的内存中,从起始位置偏移 index * s->size 字节的位置。

    3. 获取该位置的地址,并返回一个指向该地址的指针。

    示例

    假设:

    • s->storage 指向一块动态分配的内存,大小为 400 字节
      (100 个存储单元,每个单元 4 字节)。

    • s->size = 4,表示每个存储单元的大小为 4 字节。

    • 调用 fetch(s, 2),即获取索引为 2 的元素。

    执行过程:

    1. 计算偏移量

      index * s->size = 2 * 4 = 8
    2. 访问内存位置

      s->storage[8]  // 访问第 8 字节
    3. 获取地址

      &(s->storage[8])  // 获取第 8 字节的地址

    返回值是一个指向目标元素的指针,类型为 void*

    返回值

    • 返回值类型void*,表示返回的是一个通用指针,指向目标元素的地址

    • 调用方处理:调用方需要将返回的 void* 指针转换为具体的类型指针(如 int*char*),然后解引用以获取实际的值。例如:

      int* fetchedValue = (int*)fetch(&intStash, 2);
      cout << "Value at index 2: " << *fetchedValue << endl;

    总结

    • &(s->storage[index * s->size]):计算并返回指定索引 index 处的元素的地址。

    • 作用:通过计算偏移量,访问动态分配的内存中的目标元素,并返回其地址。

    • 返回值:一个指向目标元素的 void* 类型指针,调用方需要根据存储的数据类型进行适当的转换解引用

    *(int*)fetch(&intStash, i)解释

    int count(CStash* s) {
        return s->next;  // Elements in CStash
    }  
    
    //调用*(int*)fetch(&intStash, i)
    
    //fetch定义
    void* fetch(CStash* s, int index) {
        // Check index boundaries:
        assert(0 <= index);  // 确保索引非负
        if (index >= s->next) // 检查索引 index 是否超出了当前已存储的元素范围。如果索引超出范围,返回 NULL
            return 0;  
        // Produce pointer to desired element:
        return &(s->storage[index * s->size]);
    }
    for(i = 0; i < count(&intStash); i++)
          cout << "fetch(&intStash, " << i << ") = "
               << *(int*)fetch(&intStash, i)
               << endl; 

    fetch函数: fetch函数接收一个指向CStash结构体的指针和一个索引index,它返回指向存储在CStash中该索引位置元素的指针。
    由于storage是一个unsigned char*类型的指针,初始化时是=0 =NULL,
    fetch返回的也是void*类型的指针,即一个通用指针,指向任何类型的数据。

    类型转换和解引用: 当我们调用fetch(&intStash, i)时,它返回指向intStash索引为i的元素的void*类型的指针
    由于我们知道intStash是用来存储int类型元素的(因为我们之前用sizeof(int)初始化了它),我们需要将这个void*类型的指针转换为int*类型的指针,以便能够正确地解引用它以获取存储的整数值。

    • *(int*)fetch(&intStash, i)解释:

      • fetch(&intStash, i): 调用fetch函数,获取指向intStash中索引为i的元素的void*指针。
      • (int*)fetch(&intStash, i): 将这个void*指针转换为int*指针
      • *(int*)fetch(&intStash, i): 解引用这个int*指针,获取存储的整数值

    cleanup(&intStash);

    void cleanup(CStash* s) {
        if(s->storage != 0) {
            cout << "freeing storage" << endl;
            delete [ ]s->storage;
        }
    }   

    字符型结构体stringStash 解释 

    代码 仅包含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. 
    
    
    #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 
    
      // 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); 

    initialize(&stringStash, sizeof(char)*bufsize);

    • 第一个参数是 &stringStash,这是 stringStash 的地址
    • stringStash 可能是一个结构体。使用了地址运算符 &
    • 第二个参数是 sizeof(char)*bufsize。这里使用了 sizeof 运算符来计算 char 类型的大小(在大多数平台上,sizeof(char) 的值都是 1 字节),然后将其乘以 bufsize。这个表达式的目的是计算一个能够存储80个字符的数组所需的字节数
      由于 sizeof(char) 通常为 1,这个表达式简化为 1 * 80,即 80 字节
      这个值作为参数传递给 initialize 函数,可能用于为 stringStash 分配足够的内存空间来存储80个字符的字符串。

    include<fstream>
    ifstream in;
    in.open("CLibTest.cpp");
    assert(in);

    assert(in); 来检查文件是否打开是不正确的,因为 std::ifstream 对象在转换为布尔值时,只有在文件成功打开时才会转换为 true  

    while(getline(in, line))
           add(&stringStash, line.c_str());

    line.c_str() 的作用

    • line 是一个 std::string 对象。

    • c_str()std::string 类的成员函数,返回一个指向内部字符数组的 const char* 指针。

    • 返回的指针指向的字符数组以 \0 结尾,符合 C 风格字符串的格式。

    • .c_str() 在 C++ 的 std::string 类中返回的是一个指向字符串内部字符数组首个字符的指针。
      也就是说,它指向的是字符数组的第一个字符的地址,而不是末尾的空字符 \0

    • .c_str() 成员函数时,返回的 C 风格字符串(即\0 结尾的字符数组)是由 std::string 对象内部自动管理的,包括在字符串的末尾添加空字符 \0
      这个空字符 \0 是 C 风格字符串的标准表示方式,用于标识字符串的结束。

        std::string cppString = "Hello, World!";
        const char* cString = cppString.c_str();
     
        // 输出 C 风格字符串
        std::cout << cString << std::endl;


    http://www.kler.cn/a/555304.html

    相关文章:

  1. 2025软件测试面试题大全(78题含答案解析)
  2. VSCode 中使用 Snippets 设置常用代码块
  3. 大厂算法面试常见问题总结:高频考点与备战指南
  4. [数据结构] Map的使用与注意事项
  5. Python 将PPT幻灯片和形状转换为多种图片格式(JPG, PNG, BMP, SVG, TIFF)
  6. 《DeepSeek模型压缩:在高效与性能间寻平衡》
  7. LLM(十五)| Kimi k1.5:解锁语言模型强化学习新高度
  8. 2月17日c语言框架
  9. 最新扣子(Coze)案例教程:全自动DeepSeek 写影评+批量生成 + 发布飞书,提效10 倍!手把手教学,完全免费教程
  10. MySQL误删控制文件导致系统无法正常启动
  11. 标量化rknn的输入输出向量转换处理
  12. Go日期时间处理工具Carbon
  13. 深入解析:在Spring Boot中集成MyBatis Plus实现高效数据库操作
  14. 基于WebGIS技术的校园地图导航系统架构与核心功能设计
  15. 快手大数据开发平台:实践与演进之路
  16. 设计模式教程:责任链模式(Chain of Responsibility Pattern)
  17. 【Linux】 关于配置linux系统的环境变量
  18. 后端开发-分页游标设计(解决大数据量分页查询时的性能问题)
  19. 深入理解 Uber 漏桶与 Go 令牌桶限流器
  20. 边缘安全加速平台 EO 套餐