C語言:結構體基本使用、結構體常見賦值問題及解決、結構體偏移量、內存對齊問題、結構體與一級二級指針嵌套

文章可能比較長,如果想仔細瞭解結構體知識的話請大佬們耐心看完。

一、結構體基本使用

基本規則與使用方法:
1.正常結構體定義時不能賦初值;

struct Person
{
	char name[64];
	//int age = 50;//定義時不能賦初值,只有使用變量時才賦初值。
	int age;
};

2.使用typedef對結構體取別名;

typedef struct Person
{
	char name[64];
	int age;
}MyPerson;//MyPerson爲struct Person類型的別名;

void test()
{
	MyPerson p = {"aaa",10};//可以直接用別名使用;
}

3.沒有寫時typedef是定義了一個結構體變量(與上面對比),可以對其直接使用;

struct Person
{
	char name[64];
	int age;
}MyPerson = {"小米",200};//定義了一個結構體變量,可以直接使用

void test()
{
	printf("姓名:%s 年齡:%d\n",MyPerson.name,MyPerson.age);
}

輸出結果:
在這裏插入圖片描述
4.匿名類型下創建一個結構體變量,只有一個MyPerson3結構體變量可以使用,無法創建新的變量,也可對其直接使用;侷限性強,基本不會使用。

struct
{
	char name[64];
	int age;
}MyPerson3 = {"小白",30};

void test()
{
	printf("姓名:%s 年齡:%d\n",MyPerson3.name,MyPerson3.age);
}

輸出結果:
在這裏插入圖片描述
5.在棧區與堆區創建結構體變量;

void test()
{
	//在棧上創建結構體
	struct Person p1 = {"aaa",10};

	//在堆區創建結構體變量
	struct Person* p2 = (struct Person*)malloc(sizeof(struct Person));
	p2->age = 66;
	strcpy(p2->name,"bbb");
}

6.在棧上與堆上創建結構體變量數組;

typedef struct Person
{
	char name[64];
	int age;
}MyPerson;

void PrintArray(struct Person persons[],int len)
{
	for (int i=0; i<len; i++)
	{
		printf("姓名:%s  年齡:%d\n",persons[i].name,persons[i].age);
	}
}

void test()
{
	//在棧上創建結構體變量數組
	struct Person persons[] = 
	{
		{"aaa",10},
		{"bbb",20},
		{"ccc",30},
		{"ddd",40}
	};
	int len = sizeof(persons)/sizeof(struct Person);
	PrintArray(persons,len);

	//在堆區創建結構體變量數組
	struct Person* personArr = (struct Person*)malloc(sizeof(struct Person)*4);
	for (int i=0; i<4; i++)
	{
		sprintf(personArr[i].name,"name_%d",i);
		personArr[i].age = i+18;
	}
	PrintArray(personArr,4);

	if (personArr != NULL)
	{
		free(personArr);
		personArr = NULL;
	}
}

打印結果:輸出成功。
在這裏插入圖片描述

二、結構體常見賦值問題及解決

結構體常見賦值問題以及解決:
1.結構體變量賦值問題:在棧上開闢內存 : 將p2賦值給了p1

struct Person
{
	char name[64];
	int age;
};

void test()
{
	struct Person p1 = {"Tom",20};
	struct Person p2 = {"Jerry",18};

	p1 = p2;//將p2賦值給p1

	printf("p1的姓名:%s p1的年齡:%d\n",p1.name,p1.age);
	printf("p2的姓名:%s p2的年齡:%d\n",p2.name,p2.age);
}

那麼系統做了什麼操作呢?
系統提供了淺拷貝操作,將p2每個字節拷貝到p1上,拷貝成功。

在這裏插入圖片描述
輸出結果:賦值成功;
在這裏插入圖片描述
2.結構體變量賦值問題:在堆區開闢內存(淺拷貝出現的問題) :
若將上面代碼改爲如下代碼:

struct Person2
{
	char* name;
	int age;
};

void test2()
{
	struct Person2 p1;
	p1.age = 18;
	p1.name = (char*)malloc(sizeof(char)*64);
	strcpy(p1.name,"Tom");

	struct Person2 p2;
	p2.age = 20;
	p2.name = (char*)malloc(sizeof(char)*128);
	strcpy(p2.name,"Jerry");

	printf("p1的姓名:%s p1的年齡:%d\n",p1.name,p1.age);
	printf("p2的姓名:%s p2的年齡:%d\n",p2.name,p2.age);
}

先打印一下,打印成功。
在這裏插入圖片描述
此時,我們再將p2賦值給p1。

p1 = p2;//p2賦值給p1

再打印一下:還是沒問題的
在這裏插入圖片描述
但是我們此時是在堆區開闢的內存,需要手動釋放(堆區纔會出現的問題,棧區不會出現該問題),我們執行如下代碼後。

	if (p1.name != NULL)
	{
		free(p1.name);
		p1.name = NULL;
	}

	if (p2.name != NULL)
	{
		free(p2.name);
		p2.name = NULL;
	}

程序崩潰!未響應,關了半天才關掉!
在這裏插入圖片描述
爲什麼會發生這樣的結果呢?我們來研究一下:
在這裏插入圖片描述
那麼如何解決它呢?
C語言裏面解決它的方案:手動做一個賦值操作。開闢新的空間,最後釋放。
在這裏插入圖片描述
代碼操作:先將p1釋放乾淨,重新手動開闢一塊新的空間,手動使用strcpy賦值,最後再釋放。

    struct Person2
{
	char* name;
	int age;
};

void test2()
{
	struct Person2 p1;
	p1.age = 18;
	p1.name = (char*)malloc(sizeof(char)*64);
	strcpy(p1.name,"Tom");

	struct Person2 p2;
	p2.age = 20;
	p2.name = (char*)malloc(sizeof(char)*128);
	strcpy(p2.name,"Jerry");

	//p1 = p2;//p2賦值給p1
	//自己提供一個賦值操作
	//先釋放原有的內容
	if (p1.name != NULL)
	{
		free(p1.name);
		p1.name = NULL;
	}
	p1.name = (char*)malloc(strlen(p2.name)+1);
	strcpy(p1.name,p2.name);
	p1.age = p2.age;

	printf("p1的姓名:%s p1的年齡:%d\n",p1.name,p1.age);
	printf("p2的姓名:%s p2的年齡:%d\n",p2.name,p2.age);

	if (p1.name != NULL)
	{
		free(p1.name);
		p1.name = NULL;
	}

	if (p2.name != NULL)
	{
		free(p2.name);
		p2.name = NULL;
	}
}

輸出一下:打印成功,也沒有崩潰,堆區數據也釋放乾淨。
在這裏插入圖片描述

三、結構體偏移量

我們已知一個結構體:如何計算結構體中某個變量它的偏移量呢?
方式一:口算 如下,我們明白內存對齊的方式,會自動補齊;因此a補齊爲0-3,b的偏移量就是4了。

struct Person
{
	char a;//0~3
	int b;//4~7
};

方式二:利用offsetof宏
offsetof 會生成一個類型爲 size_t 的整型常量,其功能是一個結構成員相對於結構開頭的字節偏移量。

宏聲明:offsetof(type, member-designator)
參數:
type:這是一個class類型,其中,member-designator是一個有效的成員指示器。
member-designator:這是一個class類型的成員指示器。
返回值:該宏返回類型爲 size_t 的值,表示 type 中成員的偏移量。

代碼實例:

struct Person
{
	char a;//0~3
	int b;//4~7
};

void test()
{
	struct Person p1;
	struct Person* p = &p1;
	printf("b的偏移量爲:%d\n",offsetof(struct Person,b));
}

可以直接計算出來偏移量:4
在這裏插入圖片描述
方式三:利用地址計算 : 利用b的地址減去a的地址

printf("b的偏移量爲:%d\n",(int)&(p->b)-(int)p);//即b的地址減去a的地址

偏移量也爲:4
在這裏插入圖片描述
實際案例1:通過偏移量獲取結構體裏面的數據

struct Person
{
	char a;
	int b;
};

void test2()
{
	struct Person p1 = {'a',10};
	printf("p.b = %d\n",*(int*)((char*)&p1 + offsetof(struct Person,b)));
	printf("p.b = %d\n",*(int*)((int*)&p1 + 1));
}

輸出結果:
在這裏插入圖片描述
實際案例2:通過偏移量找到結構體中嵌套結構體的數據 結構體中嵌套結構體本質是將其展開。

struct Person
{
	char a;
	int b;
};

struct Person2
{
	char a;
	int b;
	struct Person c;//相當於將上面的結構體展開
};

void test()
{
	struct Person2 p = {'a',10,'c',20};

	int offset1 = offsetof(struct Person2,c);
	int offset2 = offsetof(struct Person,b);
	printf("方式1訪問偏移量爲:%d\n",*(int*)((char*)&p+offset1+offset2));//方式1
	printf("方式2訪問偏移量爲:%d\n",((struct Person*)((char*)&p+offset1))->b);//方式2
}

打印結果:成功找到
在這裏插入圖片描述

四、內存對齊問題

爲什麼需要內存對齊?
       內存的最小單元是一個字節,當CPU從內存中讀取數據的時候,是一個一個字節讀取。但是實際上CPU將內存當成多個塊,每次從內存中讀取一個塊,這個塊的大小可能是2、4、8、16等。內存對齊是操作系統爲了提高訪問內存的策略,操作系統再訪問內存的時候,每次讀取一定的長度(這個長度是操作系統默認的對齊數,或者默認對齊數的整數倍)。如果沒有對齊,爲了訪問一個變量可能產生二次訪問。內存對齊的優勢:以空間換時間。
在這裏插入圖片描述
那麼如何進行內存對齊?
1.對於標準數據類型,它的地址只要是它的長度的整數倍。
2.對於非標準數據類型,如結構體,要遵循以下對齊原則:
①從第一個屬性開始,偏移爲0;
②從第二個屬性開始,地址要放在該類型整數倍與對齊模數(系統中默認爲8)比取小的值的整數倍上。
③所有的屬性都計算完成後,整體在做二次對齊,整體需要放在屬性中最大類型與對齊模數比取效地整數倍上。
如何查看對齊模數:輸入下列代碼,重新生成下;對齊模數可以改爲2^n;

#pragma pack(show)//查看對齊模數,輸出該代碼,不要運行,重新生成以下就好了

可以看到編譯器最下面:
在這裏插入圖片描述
實例1:計算結構體大小

typedef struct _STUDENT
{
	int a;//0-3
	char b;//4-7
	double c;//8-15
	float d;//16-24
}Student;

void test()
{
	printf("sizeof = %d\n",sizeof(Student));
}

輸出結果:根據內存對齊爲24
在這裏插入圖片描述
實例2:修改對齊模數爲1(相當於沒有內存對齊,挨個相加)後計算

#pragma pack(1)
typedef struct _STUDENT
{
	int a;//0-3
	char b;//4
	double c;//5-12
	float d;//13-16
}Student;

void test()
{
	printf("sizeof = %d\n",sizeof(Student));
}

輸出結果:修改對齊模數爲1,挨個數據類型相加即爲結果。
在這裏插入圖片描述
實例3:結構體嵌套結構體時,只需要看子結構體中最大數據類型就可以了。

typedef struct _STUDENT
{
	int a;
	char b;
	double c;
	float d;//總大小爲24
}Student;

typedef struct _STUDENT2
{
	char a;//0-7
	Student b;//8-31
	double c;//32-39
}Student2;

void test()
{
	printf("sizeof = %d\n",sizeof(Student2));
}

輸出結果:總大小爲40字節
在這裏插入圖片描述

五、結構體與一級指針嵌套

我們直接看一個例子來使用它:大致在堆區創建結構如下。需求爲打印出來這裏面所有人的年齡和姓名。
大致流程如下:
①設計一個結構體struct Person{char* name,int age}
②在堆區創建結構體指針數組malloc(sizeof(struct Person*)*3);
③給每個結構體也分配到堆區
④給每個結構體的姓名分配到堆區
⑤打印數組中所有人的信息
⑥釋放堆區數據。
在這裏插入圖片描述
實現代碼:

struct Person
{
	char* name;
	int age;
};

void printArray(struct Person **pArray,int len)
{
	for (int i=0; i<len; i++)
	{
		printf("姓名:%s  年齡:%d\n",pArray[i]->name,pArray[i]->age);
	}
}

void freeSpace(struct Person** pArray,int len)
{
	for (int i=0; i<len; i++)
	{
		if (pArray[i]->name != NULL)//釋放屬性
		{
			printf("%s被釋放了\n",pArray[i]->name);
			free(pArray[i]->name);
			pArray[i]->name = NULL;
		}

		free(pArray[i]);//釋放結構體
		pArray[i] = NULL;
	}
	free(pArray);//釋放數組
	pArray = NULL;
}

struct Person** allocateSpace()
{
	struct Person** pArray = (struct Person**)malloc(sizeof(struct Person*)*3);//分配空間
	for (int i=0; i<3; i++)
	{
		//給每個結構體開闢內存
		pArray[i] = (struct Person*)malloc(sizeof(struct Person));

		//給每個結構體的姓名開闢內存
		pArray[i]->name = (char*)malloc(sizeof(char)*64);
		sprintf(pArray[i]->name,"name_%d",i+1);
		pArray[i]->age = i+20;
	}
	return pArray;
}

void test()
{
	struct Person** pArray = NULL;//利用被調函數分配內存

	pArray = allocateSpace();
	printArray(pArray,3);
	freeSpace(pArray,3);
	pArray = NULL;
}

輸出結果:成功實現結構體與一級指針嵌套使用。
在這裏插入圖片描述

六、結構體與二級指針嵌套

我們直接看一個例子來使用它:
在這裏插入圖片描述
代碼實現:

struct Teacher
{
	char* name;
	char** Students;
};

void allocateSpace(struct Teacher*** teachers)//分配內存
{
	struct Teacher** pArray = (struct Teacher**)malloc(sizeof(struct Teacher*)*3);

	for (int i=0; i<3; i++)
	{
		//給每個老師分配空間
		pArray[i] = (struct Teacher*)malloc(sizeof(struct Teacher));

		//給每個老師姓名分配空間
		pArray[i]->name = (char*)malloc(sizeof(char)*64);
		sprintf(pArray[i]->name,"Teacher_%d",i+1);

		//給老師帶的學生數組分配空間
		pArray[i]->Students = (char**)malloc(sizeof(char*)*4);//學生數組
		//給4個學生再分配內存再賦值
		for (int j=0; j<4; j++)
		{
			pArray[i]->Students[j] = (char*)malloc(sizeof(char)*64);
			sprintf(pArray[i]->Students[j],"%s_Student_%d",pArray[i]->name,j+1);
		}
	}
	*teachers = pArray;
}

void ShowArray(struct Teacher** pArray,int len)
{
	for (int i=0; i<len; i++)
	{
		printf("%s\n",pArray[i]->name);
		for (int j=0; j<4; j++)
		{
			printf("     %s\n",pArray[i]->Students[j]);
		}
	}
}

void freeSpace(struct Teacher** pArray,int len)
{
	for (int i=0; i<len; i++)
	{
		if (pArray[i]->name != NULL)//釋放老師的姓名
		{
			free(pArray[i]->name);
			pArray[i]->name = NULL;
		}
		
		for (int j=0; j<4; j++)//釋放每個學生
		{
			if (pArray[i]->Students[j] != NULL)
			{
				free(pArray[i]->Students[j]);
				pArray[i]->Students[j] = NULL;
			}
		}
		//釋放學生數組
		free(pArray[i]->Students);
		pArray[i]->Students = NULL;

		//釋放老師
		free(pArray[i]);
		pArray[i] = NULL;
	}
	//釋放老師數組
	free(pArray);
	pArray = NULL;
}

void test()
{
	struct Teacher** pArray = NULL;
	allocateSpace(&pArray);//分配內存
	ShowArray(pArray,3);//打印數組
	freeSpace(pArray,3);//釋放
	pArray = NULL;
}

打印結果:成功實現結構體與一級指針嵌套使用。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章