串有三种三种顺序串,链式串和块链式串
常用的是第一种顺序串
前两者我在这就不进行讲解了,因为就是普通的顺序表和链式表只是其中的值必须是字符而已
为啥需要引入块链式串,我们之前分析过链表的优点是操作方便,而缺点是:存储密度较小 【存储密度=数据占的内存/实际占的内存】于是可以将多个字符放在一个节点中,以克服其缺点
块链式串
#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;
}
其中我们需要注意的几点:
什么情况下使用指针指向指针
指向指针(双重指针)通常在以下情况下使用:
-
动态内存分配:当你需要在运行时分配内存块的大小时,你会使用指向指针的指针。例如,
int **ptr
可以用于分配一个二维数组。 -
函数传递参数:当你想要通过函数修改一个指针的值时,你可以传递一个指向指针的指针作为参数。
-
链表操作:在修改链表头或者在链表中插入新节点时,指向指针的指针是非常有用的。
-
返回动态分配的内存:当你在函数中动态分配内存,并希望保持该内存的生存周期超过函数的执行时长时,可以返回一个指向指针的指针。
-
修改函数内部的指针:当你想要在函数内部修改指针的值(例如将一个指针指向一个不同的内存地址)时,可以使用指向指针的指针。
-
构建复杂数据结构:在构建复杂的数据结构(例如树或图)时,指向指针的指针可以用于更方便地进行节点之间的连接。
总的来说,指向指针的指针是一种非常有用的技术,特别是在需要动态分配内存或者在函数间传递指针时。它允许你更灵活地处理内存和数据结构。
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
。这可以通过以下两种方式之一来完成:
-
栈上分配:
Node chlnode; // 栈上分配内存 initChuckLS(&chlnode);
-
堆上分配:
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
函数内部,你使用了 malloc
为 Chuck
结构体分配了内存:
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; }
在上面的例子中,myStruct
的 value
成员包含了一个垃圾值,这个值可能是任何东西,取决于这块内存之前被谁使用过。
如果你想要确保结构体成员的初始值,你需要显式地为它们赋值。例如:
MyStruct myStruct = {0}; // 将所有成员初始化为0 // 或者 MyStruct myStruct; myStruct.value = 0; // 为 value 成员赋值
这样,你就可以确保结构体的成员在声明时已经被初始化了。
那什么时候可以栈上分配内存,什么时候可以堆上分配内存
栈内存和堆内存都是在程序运行时分配内存的两种方式,它们有各自的使用场景和特点:
栈上分配内存:
-
自动分配与释放:栈内存的分配和释放是自动管理的,当你声明一个变量时,它会自动分配内存,当变量超出作用域时,内存会自动释放。
-
速度相对较快:因为栈内存的分配和释放是由编译器自动完成的,所以速度相对较快。
-
有限的空间:栈内存的大小是有限的,通常在几兆字节的范围内。如果你在栈上分配大量的数据,可能会导致栈溢出。
-
作用域限制:栈上分配的内存只在当前作用域可见,一旦超出作用域,它就会被自动释放。
堆上分配内存:
-
手动分配与释放:堆内存的分配和释放需要显式地调用
malloc
或free
等函数来完成。程序员负责管理堆内存的生命周期。 -
速度相对较慢:相对于栈内存,堆内存的分配和释放会稍微慢一些,因为它涉及到动态的内存管理。
-
空间相对较大:堆内存的空间相对来说比栈要大得多,但是也受到系统总内存的限制。
-
全局可访问:堆上分配的内存可以在程序的任何地方访问,因此它的生命周期不受作用域的限制。
使用场景:
-
栈上分配通常用于保存局部变量和临时数据。这是一种常见的方式,也是默认的内存分配方式。
-
堆上分配通常用于动态分配内存,特别是当你需要在程序运行时决定内存大小或者需要在多个作用域中共享数据时。
总的来说,选择栈内存还是堆内存取决于你的需求和程序的设计。在大多数情况下,栈内存是首选,因为它更容易使用和管理。然而,在某些情况下,你可能需要使用堆内存来满足特定的需求,比如动态分配数组或者在多个作用域中共享数据。
这种情况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
结构体的指针 head
和 tail
。这意味着这两个指针将指向堆中分配的 Chuck
对象。
如果你在栈上创建一个 Node
对象,它的 head
和 tail
指针将指向一个无效的内存区域,因为在栈上分配的内存会在函数返回时被自动释放。
如果你希望在函数返回后继续使用 Node
对象以及它的 head
和 tail
指针,你应该在堆上动态分配内存。这样,分配的内存将在你显式释放它之前一直存在。
因此,当一个结构体包含指向动态分配内存的指针时,通常需要在堆上分配内存,以保证内存的生存周期能够延续到需要的时候。
栈不能指向堆?堆不能指向栈?
栈和堆是两个不同的内存区域,它们有不同的特性和用途。在一般情况下:
-
栈:
-
存储在栈中的数据的生命周期是由程序的作用域(scope)决定的。
-
栈上的内存分配和释放是自动的,当一个变量超出其作用域时,它的内存会自动释放。
-
栈上的内存分配速度相对较快。
-
-
堆:
-
堆上分配的内存的生命周期不受作用域的限制,它需要手动释放。
-
在堆上分配内存需要使用
malloc
或者类似的函数,而释放需要使用free
。 -
堆上的内存分配速度相对较慢。
-
在C语言中,指针可以指向任何合法的内存地址,无论是栈上分配的还是堆上分配的。因此:
-
栈可以指向堆:你可以在栈上创建一个指针,并将其指向堆上分配的内存。
-
堆也可以指向栈:你可以在堆上分配一个指针,并将其指向栈上的变量。但要小心,一旦栈上的变量超出作用域,堆上的指针将会变成悬垂指针,引发未定义行为。
总的来说,要小心管理指针的生命周期,确保在合适的时机释放内存,避免内存泄漏或者出现悬垂指针的情况。
那如果你在栈上创建一个 Node 对象,它的 head 和 tail 指针将指向一个无效的内存区域,因为在栈上分配的内存会在函数返回时被自动释放。?
对,你理解的完全正确。如果你在栈上创建一个 Node
对象,它的 head
和 tail
指针将会指向栈中的内存,而这块内存在函数返回时会被自动释放。
这将导致 head
和 tail
指针成为悬垂指针(dangling pointers),它们将指向无效的内存区域。当你试图访问或修改这些指针时,将会导致未定义行为,这可能会导致程序崩溃或产生难以预测的结果。
因此,在你的情况下,由于 Node
包含了指向动态分配内存的指针(head
和 tail
),你应该在堆上动态分配内存来创建一个 Node
对象,以确保内存的生命周期能够延续到需要的时候。
这就是为什么在你的代码中,通常会使用 malloc
或者类似的函数来创建一个 Node
对象。这样做将确保在函数返回后 head
和 tail
指针仍然指向有效的内存。