c語言結構體指標初始化
今天來討論一下C中的記憶體管理。
記得上週在飯桌上和同事討論C語言的崛起時,講到了記憶體管理方面
我說所有指標使用前都必須初始化,結構體中的成員指標也是一樣
有人反駁說,不是吧,以前做二叉樹演算法時,他的左右孩子指標使用時難道有初始化嗎
那時我不知怎麼的想不出理由,雖然我還是堅信要初始化的
過了幾天這位同事說他試了一下,結構體中的成員指標不經過初始化是可以用(左子樹和右子樹指標)
那時在忙著整理文件,沒在意
今天抽空調了一下,結論是,還是需要初始化的。
而且,不寫程式碼你是不知道原因的(也許是對著電腦久了IQ和記性嚴重下跌吧)
測試程式碼如下
- #include
- #include
- #include
- struct student{
- char *name;
- int score;
- struct student* next;
- }stu,*stu1;
- int main(){
- stu.name = (char*)malloc(sizeof(char)); /*1.結構體成員指標需要初始化*/
- strcpy(stu.name,"Jimy");
- stu.score = 99;
- stu1 = (struct student*)malloc(sizeof(struct student));/*2.結構體指標需要初始化*/
- stu1->name = (
- stu.next = stu1;
- strcpy(stu1->name,"Lucy");
- stu1->score = 98;
- stu1->next = NULL;
- printf("name %s, score %d \n ",stu.name, stu.score);
- printf("name %s, score %d \n ",stu1->name, stu1->score);
- free(stu1);
- return
- }
#include #include #include struct student{ char *name; int score; struct student* next; }stu,*stu1; int main(){ stu.name = (char*)malloc(sizeof(char)); /*1.結構體成員指標需要初始化*/ strcpy(stu.name,"Jimy"); stu.score = 99; stu1 = (struct student*)malloc(sizeof(struct student));/*2.結構體指標需要初始化*/ stu1->name = (char*)malloc(sizeof(char));/*3.結構體指標的成員指標同樣需要初始化*/ stu.next = stu1; strcpy(stu1->name,"Lucy"); stu1->score = 98; stu1->next = NULL; printf("name %s, score %d \n ",stu.name, stu.score); printf("name %s, score %d \n ",stu1->name, stu1->score); free(stu1); return 0; }
寫測試程式碼的過程中我明白了,同事所說的二叉樹遍歷演算法中所用的左子樹和右子樹指標不需要初始化,其實是這樣的,左子樹和右子樹指向的必須是二叉樹節點型別的結構體指標(你填一個長度相同的指標也可以),而該結構體指標是需要初始化的(見註釋2),也就是並沒有通過malloc來分配記憶體,而是將另一個指標的值賦給它
頓時覺得挺無語的,確實,看了很多大學裡的教材,對於二叉樹的遍歷等演算法定義的結構體無非是以下形式
- struct node{
- int data;
- struct node* lchild, rchild;
- };
struct node{ int data; struct node* lchild, rchild; };
使用時都直接的
- struct node* root;
- root = (struct node*)malloc(sizeof(struct node));
- root->data = 3;
- struct node* nlchild;
- nlchild = (struct node*)malloc(sizeof(struct node));
- root->lchild = nlchild;
- nlchild->data = 2;
- struct node* nrchild;
- nlrchild = (struct node*)malloc(sizeof(struct node));
- root->rchild = nrchild;
- nrchild->data = 4;
struct node* root; root = (struct node*)malloc(sizeof(struct node)); root->data = 3; struct node* nlchild; nlchild = (struct node*)malloc(sizeof(struct node)); root->lchild = nlchild; nlchild->data = 2; struct node* nrchild; nlrchild = (struct node*)malloc(sizeof(struct node)); root->rchild = nrchild; nrchild->data = 4;
這樣子給人造成一種錯覺好像結構體成員指標是不用初始化的。
可是,只要是指標,要使用它前就必須保證指標變數的值是一個有效的值;否則,它指向的記憶體一定是垃圾資料!
C語言的記憶體管理很重要,集魄力和麻煩於一身,看你自己的心態如何了。如果你積極的面對,你正在控制一切;如果你覺得煩躁,你正不得不控制一切。C仍舊是博大精深的語言,信C哥!
/*附加:仍舊是指標*/
- stu1 = (struct student*)malloc(sizeof(struct student));/*2.結構體指標需要初始化*/
stu1 = (struct student*)malloc(sizeof(struct student));/*2.結構體指標需要初始化*/
這一句可能會有人把sizeof裡邊也填成struct student*
可以理解這樣的行為,因為stu本來就是struct student*,可是這樣子你就沒有為結構體分配足夠的記憶體,使用中會因為記憶體錯誤同樣報錯的。
當然,僅僅為結構體指標分配記憶體還不夠,結構體成員指標仍然需要分配記憶體,如下
- stu1->name = (char*)malloc(sizeof(char));
自己在用結構體指標的時候遇到的引用問題,網上找的一段文字覺得挺不錯的,可能對大家有幫助。
在使用結構體指標變數的時候,往往容易犯一個“低階”錯誤。即定義一個結構體指標變數後就直接對結構體指標變數所指向的結構體成員進行操作,從而產生一些莫名其妙的錯誤。我們必須要給結構體指標變數賦予一個有效的結構體變數地址,才能正常操作結構體指標變數。比如:
struct UART{
int a;
uchar b;
}
main()
{
struct UART *p;
p->a = 0xXXX;
p->b = 0xXX;
printf("%i,%c",p->b,p->a);
}
這個程式輸出的值將是不可預知的,因為“在程式中只是定義了一個結構體指標變數,並沒有給該結構體指標變數賦一個有效值,因此該結構體變數所指向的地址將不確定,從而不能得到預期結果”
應該改為:
struct UART{
int a;
uchar b;
}
main()
{
struct UART *p;
struct UART dd;
p = ⅆ //這句一定要有,否則將出現不可預知的問題
p->a = 0xXXX;
p->b = 0xXX;
printf("%i,%c",p->b,p->a);
}
C/C++中
結構體(struct)知識點強化 為了進一部的學習結構體這一重要的知識點,我們今天來學習一下連結串列結構。
結構體可以看做是一種自定義的資料型別,它還有一個很重要的特性,就是結構體可以相互巢狀使用,但也是有條件的,結構體可以包含結構體指標,但絕對不能在結構體中包含結構體變數。
struct test
{
char name[10];
float socre;
test *next;
};//這樣是正確的!
struct test
{
char name[10];
float socre;
test next;
};//這樣是錯誤的!
利用結構體的這點特殊特性,我們就可以自己生成一個環環相套的一種射線結構,一個指向另一個。
連結串列的學習不像想象的那麼那麼容易,很多人學習到這裡的時候都會碰到困難,很多人也因此而放棄了學習,在這裡我說,一定不能放棄,對應它的學習我們要進行分解式學習,方法很重要,理解需要時間,不必要把自己逼迫的那麼緊,學習前你也得做一些最基本的準備工作,你必須具備對堆記憶體的基本知識的瞭解,還有就是對結構體的基本認識,有了這兩個重要的條件,再進行分解式學習就可以比較輕鬆的掌握這一節內容的難點。
下面我們給出一個完整的建立連結串列的程式,不管看的懂看不懂希望讀者先認真看一下,想一想,看不懂沒有關係,因為我下面會有分解式的教程,但之前的基本思考一定要做,要不即使我分解了你也是無從理解的。
程式碼如下,我在重要部分做了註解:
#include
using namespace std;
struct test
{
char name[10];
float socre;
test *next;
};
test *head;//建立一個全域性的引導進入連結串列的指標
test *create()
{
test *ls;//節點指標
test *le;//鏈尾指標
ls = new test;//把ls指向動態開闢的堆記憶體地址
cin>>ls->name>>ls->socre;
head=NULL;//進入的時候先不設定head指標指向任何地址,因為不知道是否一上來就輸入null跳出程式
le=ls;//把鏈尾指標設定成剛剛動態開闢的堆記憶體地址,用於等下設定le->next,也就是下一個節點的位置
while(strcmp(ls->name,"null")!=0)//建立迴圈條件為ls->name的值不是null,用於迴圈新增節點
{
if(head==NULL)//判斷是否是第一次進入迴圈
{
head=ls;//如果是第一次進入迴圈,那麼把引導進入連結串列的指標指向第一次動態開闢的堆記憶體地址
}
else
{
le->next=ls;//如果不是第一次進入那麼就把上一次的鏈尾指標的le->next指向上一次迴圈結束前動態建立的堆記憶體地址
}
le=ls;//設定鏈尾指標為當前迴圈中的節點指標,用於下一次進入迴圈的時候把上一次的節點的next指向上一次迴圈結束前動態建立的堆記憶體地址
ls=new test;//為下一個節點在堆記憶體中動態開闢空間
cin>>ls->name>>ls->socre;
}
le->next=NULL;//把鏈尾指標的next設定為空,因為不管如何迴圈總是要結束的,設定為空才能夠在迴圈顯連結串列的時候不至於死迴圈
delete ls;//當結束的時候最後一個動態開闢的記憶體是無效的,所以必須清除掉
return head;//返回鏈首指標
}
void showl(test *head)
{
cout<<"鏈首指標:"< <
while(head)//以記憶體指向為null為條件迴圈顯示先前輸入的內容
{
cout< name<<"|"< socre<
head=head->next;
}
}
void main()
{
showl(create());
cin.get();
cin.get();
}
上面的程式碼我們是要達到一個目的:就是要儲存你輸入的人名和他們的得分,並且以鏈狀結構把它們組合成一個鏈狀結構。
程式種有兩個組成部分
test *create()
和 void showl(test *head)
這兩個函式,create是用來建立連結串列的 ,showl是用來顯示連結串列的。
create函式的返回型別是一個結構體指標,在程式呼叫的時候我們用了showl(create());,而不用引用的目的原因是引導指標是一個全域性指標變數,我們不能在showl()內改變它,因為showl()函式內有一個移動操作head=head->next;,如果是引用的話我們就破壞了head指標的位置,以至於我們再也無法找會首地址的位置了。
下面我們來分解整個程式,以一個初學者的思想來思考整個程式,由淺入深的逐步解釋。
首先,我們寫這個程式,要考慮到由於是一個連結串列結構,我們不可能知道它的大小到底是多大,這個問題我們可以用動態開闢堆記憶體來解決,因為堆記憶體在程式結束前始終是有效的,不受函式棧空間生命期的限制,但要注意的是我們必須有一個指標變數來儲存這一鏈狀結構的進入地址,而在函式內部來建立這一指標變數顯然是不合適的,因為函式一旦退出,這個指標變數也隨之失效,所以我們在程式的開始聲明瞭一個全域性指標變數。
test *head;//建立一個全域性的引導進入連結串列的指標
好解決了這兩個問題,我們接下去思考
有輸入就必然有輸出,由於輸出函式和輸入函式是相對獨立的,為了不斷測試程式的正確性好除錯我們先寫好輸出函式和main函式捏的呼叫,建立函式我們先約定好名為create。
我們先寫出如下的程式碼:
#include
using namespace std;
struct test
{
char name[10];
float socre;
test *next;
};
test *head;//建立一個全域性的引導進入連結串列的指標
test *create()
{
return head;//返回鏈首指標
}
void showl(test *head)
{
cout<<"鏈首指標:"< <
while(head)//以記憶體指向為null為條件迴圈顯示先前輸入的內容
{
cout< name<<"|"< socre<
head=head->next;
}
}
void main()
{
showl(create());
cin.get();
cin.get();
}
程式寫到這裡,基本形態已經出來,輸入和呼叫我們已經有了。
下面我們來解決輸入問題,連結串列的實現我們是通過迴圈輸入來實現的,既然是迴圈我們就一定得考慮終止迴圈的條件,避免死迴圈和無效迴圈的發生。
在create()函式內部我們先寫成這樣:
test *create()
{
test *ls;//節點指標
test *le;//鏈尾指標
ls = new test;//把ls指向動態開闢的堆記憶體地址
cin>>ls->name>>ls->socre;
head=NULL;//進入的時候先不設定head指標指向任何地址,因為不知道是否一上來就輸入null跳出程式
le=ls;//把鏈尾指標設定成剛剛動態開闢的堆記憶體地址,用於等下設定le->next,也就是下一個節點的位置
le->next=NULL;//把鏈尾指標的next設定為空,因為不管如何迴圈總是要結束的,設定為空才能夠在迴圈顯連結串列的時候不至於死迴圈
delete ls;//當結束的時候最後一個動態開闢的記憶體是無效的,所以必須清除掉
return head;//返回鏈首指標
}
在迴圈建立之前我們必須考慮一個都不輸入的情況。
程式一單進入create函式我們首先必然要建立一個節點,我們先建立一個節點指標,後把者個節點指標指向到動態開闢的test型別的動態記憶體地址位置上。
test *ls;
ls = new test;
程式既然是迴圈輸入,而結構成員test *next又是用來儲存下一個接點的記憶體地址的,每次迴圈我們又要動態建立一個新的記憶體空間,所以我們必須要有一個指標來儲存上一次迴圈動態開闢的記憶體地址,於是就有了
test *le;
接下來在進入迴圈前我們要建立連結串列的第一個節點,第一個節點必然是在迴圈外建立,於是就有了
cin>>ls->name>>ls->socre;
程式執行者的情況是位置的,所以我們必然要考慮,一上來就不想繼續執行程式的情況,所以我們一開始先把head引導指標設定為不指向任何地址也就是
head=NULL;
為了符合le也就是鏈尾指標的設計思路,我們在迴圈前一定要儲存剛剛動態開闢的記憶體地址,好在下一次迴圈的時候設定上一個節點中的next成員指向,於是我們便有了:
le=ls;