块链串的实现(c语言)

news2024/11/28 6:41:26

串有三种三种顺序串,链式串和块链式串

常用的是第一种顺序串

前两者我在这就不进行讲解了,因为就是普通的顺序表和链式表只是其中的值必须是字符而已

为啥需要引入块链式串,我们之前分析过链表的优点是操作方便,而缺点是:存储密度较小 【存储密度=数据占的内存/实际占的内存】于是可以将多个字符放在一个节点中,以克服其缺点

 块链式串
#include <stdio.h>
#include <stdlib.h>

#define MaxSize  3
 typedef struct Chuck{
 	char chars[MaxSize];
 	struct Chuck *next;
 	int count;
 }Chuck;
 	
 typedef struct Node{
 	Chuck *head,*tail;
 	int length;
 }*ChlNode,Node;
 
 //初始化一个块链式串 
 void initChuckLS(ChlNode *chlnode){
 	Chuck *chucks=(Chuck*)malloc(sizeof(Chuck));
 	(*chlnode)=(ChlNode)malloc(sizeof(Node));
 	chucks->next=NULL;
 	chucks->count=0;
 	(*chlnode)->head=chucks;
 	(*chlnode)->tail=chucks;
 	(*chlnode)->length=1;
 }
 //添加数据 	
 void pushValue(char value,ChlNode chlnode){
 	if(chlnode->head==chlnode->tail){
 		chlnode->head->chars[chlnode->head->count]=value;
		++chlnode->head->count;
	 }else{
	 	chlnode->tail->chars[chlnode->tail->count]=value;
	 	++chlnode->tail->count;
	 }
	 if(chlnode->head->count==MaxSize){
 			Chuck *chuck=(Chuck*)malloc(sizeof(Chuck));
 			chuck->count=0;
 			chuck->next=NULL;
 			chlnode->tail->next=chuck; 
 			chlnode->tail=chuck;
 			++chlnode->length;
	}
 }
 //打印块链串 
 void printAll(ChlNode chlnode){
 	if(chlnode->head==chlnode->tail&&chlnode->head->count==0){
 		printf("空块链串");
 		return ;
	 }else{
	 	 int tmp;
	 	 Chuck *tmpPort=chlnode->head;
		 do{
		 	for(tmp=0;tmp<tmpPort->count;++tmp){
	     		char value=tmpPort->chars[tmp];
	     		printf("%c ",value);
		 	}
			tmpPort=tmpPort->next;	
		 }while(tmpPort);
	 } 
 	
 }
  
 
 
 int main(){
 	ChlNode chlnode;
 	initChuckLS(&chlnode);
 	pushValue('I',chlnode);
 	pushValue('L',chlnode);
 	pushValue('O',chlnode);
 	pushValue('V',chlnode);
 	pushValue('E',chlnode);
 	pushValue('Y',chlnode);
 	pushValue('O',chlnode);
 	pushValue('U',chlnode);
 	printAll(chlnode);
 	
 	system("pause");
 	return 0;
 }

其中我们需要注意的几点:

什么情况下使用指针指向指针

指向指针(双重指针)通常在以下情况下使用:

  1. 动态内存分配:当你需要在运行时分配内存块的大小时,你会使用指向指针的指针。例如,int **ptr 可以用于分配一个二维数组。

  2. 函数传递参数:当你想要通过函数修改一个指针的值时,你可以传递一个指向指针的指针作为参数。

  3. 链表操作:在修改链表头或者在链表中插入新节点时,指向指针的指针是非常有用的。

  4. 返回动态分配的内存:当你在函数中动态分配内存,并希望保持该内存的生存周期超过函数的执行时长时,可以返回一个指向指针的指针。

  5. 修改函数内部的指针:当你想要在函数内部修改指针的值(例如将一个指针指向一个不同的内存地址)时,可以使用指向指针的指针。

  6. 构建复杂数据结构:在构建复杂的数据结构(例如树或图)时,指向指针的指针可以用于更方便地进行节点之间的连接。

总的来说,指向指针的指针是一种非常有用的技术,特别是在需要动态分配内存或者在函数间传递指针时。它允许你更灵活地处理内存和数据结构。

typedef struct { int value; } MyStruct; void modifyStruct(MyStruct* s) { s->value = 42; } int main() { MyStruct myStruct; // 这里只是声明了一个结构体变量,没有初始化 modifyStruct(&myStruct); // 将结构体的地址传递给函数 printf("%d\n", myStruct.value); // 输出修改后的值,将会是 42 return 0; } 为啥这个就不需要初始化

在你的代码中,MyStruct myStruct; 这一行实际上是声明了一个名为 myStruct 的结构体变量,而不是初始化。在这个声明之后,myStruct 的内存分配由编译器自动完成。

如果你没有对结构体进行初始化,它的成员会包含随机的值(垃圾值)。在这个例子中,myStruct.value 将会包含一个随机的整数值。

当你将 &myStruct 传递给 modifyStruct 函数时,实际上是传递了 myStruct 的地址,这使得 modifyStruct 函数可以在函数内部直接访问并修改 myStruct 的成员。这样,通过指针的引用,你可以在函数内部修改传递的结构体。

modifyStruct 函数中,s->value = 42; 语句将会将 myStruct.value 的值修改为 42

所以,你在 printf 语句中打印的是 42,而不是 myStruct 初始化时的值,因为它在 modifyStruct 中被修改了。

//初始化一个块链式串 void initChuckLS(ChlNode *chlnode){ Chuck chucks=(Chuck)malloc(sizeof(Chuck)); // (chlnode)=(ChlNode)malloc(sizeof(Node)); chucks->next=NULL; chucks->count=0; (chlnode)->head=chucks; (chlnode)->tail=chucks; (chlnode)->length=1; }为啥chlnode不进行初始化就报错,不是所内存可由编译器自动完成吗?

在你提供的代码中,initChuckLS 函数接受一个指向 ChlNode 指针的指针作为参数,并在函数内部尝试初始化它。

如果你在调用 initChuckLS 函数之前没有正确初始化 chlnode,就会导致错误。

在调用 initChuckLS 函数之前,请确保你已经分配了内存给 ChlNode。这可以通过以下两种方式之一来完成:

  1. 栈上分配

Node chlnode;  // 栈上分配内存
initChuckLS(&chlnode);
  1. 堆上分配

Node *chlnode = (Node*)malloc(sizeof(Node));  // 堆上分配内存
initChuckLS(&chlnode);

在你提供的代码中,你在函数内部使用了 malloc 分配了内存给 chucks。但是,你没有提供代码来确保 chlnode 指向的内存是有效的。请确保在调用 initChuckLS 函数之前,chlnode 指向的内存是合法的。

另外,你注释掉了 (*chlnode)=(ChlNode)malloc(sizeof(Node)); 这一行,这可能导致 chlnode 指向的内存没有被正确分配。如果你需要在函数内部分配内存给 chlnode,请确保它的指针是合法的。

int main(){ ChlNode chlnode; initChuckLS(&chlnode);,那我这算是栈上分配了呀

对的,你在 main 函数中声明了一个 chlnode 变量,这是在栈上分配内存。然后,你将 chlnode 的地址传递给 initChuckLS 函数。

根据你提供的代码,应该没有问题。如果你在调用 initChuckLS 之前的代码中没有其他问题,那么 chlnode 应该是一个有效的指针,可以被 initChuckLS 函数正确使用。

如果你还有其他问题或者遇到了错误,请提供更多的上下文信息,我将尽力帮助你解决。

但为什么我在initChuckLS不进行堆上的初始化去为这个结构体的属性赋值就会报错

如果在 initChuckLS 函数内部没有为 Chuck 结构体的属性赋值就会导致错误,这可能是因为你没有正确地为 Chuck 结构体的成员分配内存或者赋予合适的值。

根据你提供的代码,我可以看到在 initChuckLS 函数内部,你使用了 mallocChuck 结构体分配了内存:

Chuck *chucks = (Chuck*)malloc(sizeof(Chuck));

这样做是正确的,因为你需要在堆上动态分配内存来存储 Chuck 结构体。

接下来,你为 chucks 结构体的成员赋值:

chucks->next = NULL;
chucks->count = 0;

这也是正确的,因为你在初始化 Chuck 结构体的同时,需要将其成员初始化为合适的值。

如果你在 initChuckLS 函数中的其他部分(不包括上述代码)出现了错误,请提供更多的代码或者具体的错误信息,我将尽力帮助你解决。

在栈上分配内存不行吗?typedef struct { int value; } MyStruct; void modifyStruct(MyStruct* s) { s->value = 42; } int main() { MyStruct myStruct; // 这里只是声明了一个结构体变量,没有初始化 modifyStruct(&myStruct); // 将结构体的地址传递给函数 printf("%d\n", myStruct.value); // 输出修改后的值,将会是 42 return 0; } 就像这样

在你提供的例子中,MyStruct myStruct; 这一行在主函数中声明了一个 MyStruct 结构体变量,而不是初始化。

在C语言中,当你声明一个结构体变量时,它会在栈上分配内存,但它的成员将会包含垃圾值。

在你调用 modifyStruct(&myStruct); 时,你将 myStruct 的地址传递给了 modifyStruct 函数。这使得函数可以在函数内部直接访问并修改 myStruct 的成员。

所以,通过指针的引用,你可以在函数内部修改传递的结构体。

在你的例子中,printf("%d\n", myStruct.value); 会输出 42,这是因为在 modifyStruct 中将 myStruct.value 修改为了 42

总的来说,在C语言中,你可以在栈上分配结构体,并将它们作为参数传递给函数,从而实现在函数内部修改结构体的目的。

成员将会包括垃圾值,仔细讲讲

当你声明一个结构体变量时,它的成员(如果是基本数据类型)会包含垃圾值。这是因为在栈上分配内存时,这块内存可能已经被其他程序使用过,所以里面的值可能是之前存留下来的。

举个例子:

typedef struct {
    int value;
} MyStruct;
​
int main() {
    MyStruct myStruct; // 在栈上分配内存,但成员 value 的初始值是垃圾值
​
    printf("%d\n", myStruct.value); // 输出成员 value 的值,这是一个垃圾值
​
    return 0;
}

在上面的例子中,myStructvalue 成员包含了一个垃圾值,这个值可能是任何东西,取决于这块内存之前被谁使用过。

如果你想要确保结构体成员的初始值,你需要显式地为它们赋值。例如:

MyStruct myStruct = {0}; // 将所有成员初始化为0
​
// 或者
​
MyStruct myStruct;
myStruct.value = 0; // 为 value 成员赋值

这样,你就可以确保结构体的成员在声明时已经被初始化了。

那什么时候可以栈上分配内存,什么时候可以堆上分配内存

栈内存和堆内存都是在程序运行时分配内存的两种方式,它们有各自的使用场景和特点:

栈上分配内存

  1. 自动分配与释放:栈内存的分配和释放是自动管理的,当你声明一个变量时,它会自动分配内存,当变量超出作用域时,内存会自动释放。

  2. 速度相对较快:因为栈内存的分配和释放是由编译器自动完成的,所以速度相对较快。

  3. 有限的空间:栈内存的大小是有限的,通常在几兆字节的范围内。如果你在栈上分配大量的数据,可能会导致栈溢出。

  4. 作用域限制:栈上分配的内存只在当前作用域可见,一旦超出作用域,它就会被自动释放。

堆上分配内存

  1. 手动分配与释放:堆内存的分配和释放需要显式地调用 mallocfree 等函数来完成。程序员负责管理堆内存的生命周期。

  2. 速度相对较慢:相对于栈内存,堆内存的分配和释放会稍微慢一些,因为它涉及到动态的内存管理。

  3. 空间相对较大:堆内存的空间相对来说比栈要大得多,但是也受到系统总内存的限制。

  4. 全局可访问:堆上分配的内存可以在程序的任何地方访问,因此它的生命周期不受作用域的限制。

使用场景

  • 栈上分配通常用于保存局部变量和临时数据。这是一种常见的方式,也是默认的内存分配方式。

  • 堆上分配通常用于动态分配内存,特别是当你需要在程序运行时决定内存大小或者需要在多个作用域中共享数据时。

总的来说,选择栈内存还是堆内存取决于你的需求和程序的设计。在大多数情况下,栈内存是首选,因为它更容易使用和管理。然而,在某些情况下,你可能需要使用堆内存来满足特定的需求,比如动态分配数组或者在多个作用域中共享数据。

这种情况Node结构体是需要堆上分配内存的吧

对的,你的 Node 结构体包含了指向 Chuck 结构体的指针,因此你通常会选择在堆上分配内存来创建一个 Node 对象。

如果你在函数中创建一个 Node 对象,并希望在函数返回后保持其存在,可以使用 malloc 来分配内存:

Node* createNode() {
    Node* newNode = (Node*)malloc(sizeof(Node));
    if (newNode == NULL) {
        // 内存分配失败的处理
        return NULL;
    }
    newNode->head = NULL; // 初始化成员
    newNode->tail = NULL;
    newNode->length = 0;
    return newNode;
}
​
void destroyNode(Node* node) {
    free(node);
}

在这个例子中,createNode 函数在堆上动态分配了一个 Node 对象,并返回了指向它的指针。在函数结束后,这个内存块将会保持存在。

记得在不再需要使用的时候调用 destroyNode 函数来释放分配的内存。

为啥呢?

在你的结构体中,Node 包含了指向 Chuck 结构体的指针 headtail。这意味着这两个指针将指向堆中分配的 Chuck 对象。

如果你在栈上创建一个 Node 对象,它的 headtail 指针将指向一个无效的内存区域,因为在栈上分配的内存会在函数返回时被自动释放。

如果你希望在函数返回后继续使用 Node 对象以及它的 headtail 指针,你应该在堆上动态分配内存。这样,分配的内存将在你显式释放它之前一直存在。

因此,当一个结构体包含指向动态分配内存的指针时,通常需要在堆上分配内存,以保证内存的生存周期能够延续到需要的时候。

栈不能指向堆?堆不能指向栈?

栈和堆是两个不同的内存区域,它们有不同的特性和用途。在一般情况下:

    • 存储在栈中的数据的生命周期是由程序的作用域(scope)决定的。

    • 栈上的内存分配和释放是自动的,当一个变量超出其作用域时,它的内存会自动释放。

    • 栈上的内存分配速度相对较快。

    • 堆上分配的内存的生命周期不受作用域的限制,它需要手动释放。

    • 在堆上分配内存需要使用 malloc 或者类似的函数,而释放需要使用 free

    • 堆上的内存分配速度相对较慢。

在C语言中,指针可以指向任何合法的内存地址,无论是栈上分配的还是堆上分配的。因此:

  • 栈可以指向堆:你可以在栈上创建一个指针,并将其指向堆上分配的内存。

  • 堆也可以指向栈:你可以在堆上分配一个指针,并将其指向栈上的变量。但要小心,一旦栈上的变量超出作用域,堆上的指针将会变成悬垂指针,引发未定义行为。

总的来说,要小心管理指针的生命周期,确保在合适的时机释放内存,避免内存泄漏或者出现悬垂指针的情况。

那如果你在栈上创建一个 Node 对象,它的 head 和 tail 指针将指向一个无效的内存区域,因为在栈上分配的内存会在函数返回时被自动释放。?

对,你理解的完全正确。如果你在栈上创建一个 Node 对象,它的 headtail 指针将会指向栈中的内存,而这块内存在函数返回时会被自动释放。

这将导致 headtail 指针成为悬垂指针(dangling pointers),它们将指向无效的内存区域。当你试图访问或修改这些指针时,将会导致未定义行为,这可能会导致程序崩溃或产生难以预测的结果。

因此,在你的情况下,由于 Node 包含了指向动态分配内存的指针(headtail),你应该在堆上动态分配内存来创建一个 Node 对象,以确保内存的生命周期能够延续到需要的时候。

这就是为什么在你的代码中,通常会使用 malloc 或者类似的函数来创建一个 Node 对象。这样做将确保在函数返回后 headtail 指针仍然指向有效的内存。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1096600.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

USB协议学习(一)帧格式以及协议抓取

USB协议学习&#xff08;一&#xff09;帧格式以及协议抓取 笔者来聊聊MPU的理解 这里写自定义目录标题 USB协议学习&#xff08;一&#xff09;帧格式以及协议抓取MPU的概念以及作用MPU的配置新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式…

数据挖掘(5)分类数据挖掘:基于距离的分类方法

一、分类挖掘的基本流程 最常用的就是客户评估 1.1分类器概念 1.2分类方法 基于距离的分类方法决策树分类方法贝叶斯分类方法 1.3分类的基本流程 步骤 建立分类模型 通过分类算法对训练集训练&#xff0c;得到有指导的学习、有监督的学习预定义的类&#xff1a;类标号属性确定…

【UE4 材质编辑篇】1.0 shader编译逻辑

UE4新手&#xff0c;学起来&#xff08;&#xff09;文章仅记录自己的思考。 参考&#xff1a;虚幻4渲染编程(材质编辑器篇)【第一卷&#xff1a;开篇基础】 - 知乎 (zhihu.com) 开篇基础就摸不着头脑&#xff0c;原因是此前完全没有摸过UE4&#xff0c;一点一点记录吧&#x…

18 | 生产环境多数据源的处理方法有哪些

工作中我们时常会遇到跨数据库操作的情况&#xff0c;这时候就需要配置多数据源&#xff0c;那么如何配置呢&#xff1f;常用的方式及其背后的原理支撑是什么呢&#xff1f;我们下面来了解一下。 首先看看两种常见的配置方式&#xff0c;分别为通过多个 Configuration 文件、利…

绘制多个子图fig.add_subplot函数

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 绘制多个子图 fig.add_subplot函数 下列代码创建的子图网格大小是&#xff1f; import matplotlib.pyplot as plt fig plt.figure() ax fig.add_subplot(121) ax.plot([1, 2, 3, 4, 5], [1…

做情绪识别,有必要用LLM吗?

卷友们好&#xff0c;我是尚霖。 情绪识别在各种对话场景中具有广泛的应用价值。例如&#xff0c;在社交媒体中&#xff0c;可以通过对评论进行情感分析来了解用户的情绪态度&#xff1b;在人工客服中&#xff0c;可以对客户的情绪进行分析&#xff0c;以更好地满足其需求。 此…

堆与堆排序

一.什么是堆&#xff1f; 1.堆是完全二叉树&#xff0c;除了树的最后一层结点不需要是满的&#xff0c;其它的每一层从左到右都是满的&#xff0c;如果最后一层结点不是满的&#xff0c;那么要求左满右不满。 2.堆分为两类&#xff0c;大根堆和小根堆。 大根堆每个结点都大于…

C++位图,布隆过滤器

本期我们来学习位图&#xff0c;布隆过滤器等相关知识&#xff0c;以及模拟实现&#xff0c;需求前置知识 C-哈希Hash-CSDN博客 C-封装unordered_KLZUQ的博客-CSDN博客 目录 位图 布隆过滤器 海量数据面试题 全部代码 位图 我们先来看一道面试题 给 40 亿个不重复的无符号…

scratch时间游戏 2023年9月中国电子学会图形化编程 少儿编程 scratch编程等级考试三级真题和答案解析

目录 scratch时间游戏 一、题目要求 1、准备工作 2、功能实现 二、案例分析

EtherCAT报文-FPWR(配置地址写)抓包分析

0.工具准备 1.EtherCAT主站 2.EtherCAT从站&#xff08;本文使用步进电机驱动器&#xff09; 3.Wireshark1.EtherCAT报文帧结构 EtherCAT使用标准的IEEE802.3 Ethernet帧结构&#xff0c;帧类型为0x88A4。EtherCAT数据包括2个字节的数据头和44-1498字节的数据。数据区由一个或…

芯片学习记录TLP184

TLP184 芯片介绍 TLP184是一款光耦隔离器&#xff0c;它的主要特点包括&#xff1a;高电压耐受能力、高传输速度、高共模隔离能力、低功耗等。它可以用于工业自动化、通信设备、家用电器等领域的电气隔离应用。由一个光电晶体管组成&#xff0c;光学耦合到两个红外发射二极管…

[初始java]——规范你的命名规则,变量的使用和注意事项,隐式转化和强制转化

目录 一、标识符是么 二、命名规则 三、变量 1.定义变量的完整格式&#xff1a; 2.变量的分类 3.变量在内存中的位置 4.注意事项&#xff1a; 四、隐式转化和强制转化 五、表达式和语句的概念 一、标识符是么? 就是给类、变量、方法起名字的&#xff0c;用于标识它们。…

内网、外网、宽带、带宽、流量、网速之间的区别与联系

一.带宽与宽带的区别是什么&#xff1f; 带宽是量词&#xff0c;指的是网速的大小&#xff0c;比如1Mbps的意思是一兆比特每秒&#xff0c;这个数值就是指带宽。 宽带是名词&#xff0c;说明网络的传输速率速很高 。宽带的标准各不相同&#xff0c;最初认为128kbps以上带宽的就…

从0开始学Java:Java基础语法

文章目录 1. 注释2. 关键字&#xff08;*Keyword*&#xff09;3. 标识符( Identifier)4. 常量&#xff08;*Constant*&#xff09;5. 输出语句6. 变量&#xff08;*Variable*&#xff09;7. 计算机如何存储数据7.1 进制7.2 计算机存储单位7.3 二进制数据存储 8. 数据类型8.1 数…

一行 Python 代码搞定训练分类或回归模型

引言 自动机器学习(Auto-ML)是指自动化数据科学模型开发流水线的组件。AutoML 减少了数据科学家的工作量&#xff0c;并加快了工作流程。AutoML 可用于自动化各种流水线组件&#xff0c;包括数据理解&#xff0c;EDA&#xff0c;数据处理&#xff0c;模型训练&#xff0c;超参数…

C++11(lambda表达式)

目录 一、lambda表达式的引入 二、语法格式 三、捕捉方式 四、lambda表达式的底层 1、仿函数的调用 2、lambda的调用 ​编辑 一、lambda表达式的引入 在之前&#xff0c;我们调用函数的方式有&#xff1a;通过函数指针调用&#xff0c;仿函数也能像函数一样调用。而在C…

pgsl基于docker的安装

1. 有可用的docker环境 &#xff0c;如果还没有安装docker&#xff0c;则请先安装docker 2. 创建pg数据库的挂载目录 mkdir postgres 3. 下载pg包 docker pull postgres 这个命令下载的是最新的pg包&#xff0c;如果要指定版本的话&#xff0c;则可以通过在后面拼接 :versio…

12 | JPA 的审计功能解决了哪些问题

Auditing 指的是什么&#xff1f; Auditing 是帮我们做审计用的&#xff0c;当我们操作一条记录的时候&#xff0c;需要知道这是谁创建的、什么时间创建的、最后修改人是谁、最后修改时间是什么时候&#xff0c;甚至需要修改记录……这些都是 Spring Data JPA 里面的 Auditing…

【LeetCode刷题(数据结构)】:给定一个链表 返回链表开始入环的第一个节点 如果链表无环 则返回 NULL

给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos…

【Eclipse】查看版本号

1.在Eclipse的启动页面会出现版本号 2. Eclipse的关于里面 Help - About Eclipse IDE 如下图所示&#xff0c;就为其版本 3.通过查看readme_eclipse.html文件