從0到1構造連結串列

GJ504b發表於2024-11-29
#define N 20
typedef enum{
	Male,
	Female
}gender_type;

typedef struct stu{
	char name[N];
	unsigned char age;
	gender_type gender;
	struct stu *next;
}student;

void print_info(student *p){
	while(p != NULL){
		printf("%s:%d %d\n",p ->name,p ->age, p -> gender);
		p = p -> next;
	}
}

#include<stdio.h>
int main(){
	student s1 = {"1-zhangsan", 20, Male, NULL};
	student s2 = {"2-lisi", 21, Female, NULL};
	student s3 = {
		.name = "3-wangwu",
		.age = 19,
		.gender = Male,
		.next = NULL
	};//無輸入型別
	
	s1.next = &s2;  
	s2.next = &s3;
	print_info(&s1);	
	return 0;
}



從0到1構造連結串列

本文綜合很多知識,適合基礎不牢的同學看,基本功紮實的同學可以直接跳到連結串列

靜態連結串列

結構體

  • 概念
    結構體(struct)是一種使用者自定義的資料型別,它允許你將不同型別的資料組合在一起
  • 定義
struct Student {
    ①char name[50];//字元陣列
    int age;
    float grade;

};
  • 初始化
    定義同時初始化(適用於全域性變數和靜態變數情況)
    對於全域性或靜態的結構體變數,可以在定義時進行初始化

// 這裡直接按照結構體成員定義的順序,用相應型別的值來初始化各個成員。
struct Student s1 = {"Alice", 20, 90.5};
//也可以指定具體成員進行初始化,不依賴順序,C99支援
struct Student s3 = {.score = 92.0,.age = 21,.name = "Charlie"};
struct Student s3 = {.score = 92.0,.name = "Charlie"};

  • 結構體的輸入[可以不用初始化]
    逐個成員輸入(透過 scanf 等函式)
    可以使用 scanf 等輸入函式對結構體的各個成員進行逐個輸入,例如:
#include <stdio.h>

struct Student {
    char name[50];
    int age;
    float score;
};

int main() {
    struct Student s;
    printf("請輸入學生姓名:");
    scanf("%s", s.name);
    printf("請輸入學生年齡:");
    scanf("%d", &s.age);
    printf("請輸入學生成績:");
    scanf("%f", &s.score);
    return 0;
}

注意,對於字元陣列型別的成員(如 name),在使用 scanf 輸入時,不需要取地址符 &,因為陣列名本身就代表首地址其他照舊

使用函式輔助輸入(提高程式碼複用性和可讀性)
可以編寫一個函式專門用於輸入結構體變數的資訊,例如:

#include <stdio.h>

struct Student {
    char name[50];
    int age;
    float score;
};

void inputStudent(struct Student *s) {
    printf("請輸入學生姓名:");
    scanf("%s", s->name);
    printf("請輸入學生年齡:");
    scanf("%d", &s->age);
    printf("請輸入學生成績:");
    scanf("%f", &s->age);
}

int main() {
    struct Student s;
    inputStudent(&s);
    return 0;
}

這裡透過指標傳遞結構體變數地址給函式,在函式內部使用指標運算子 -> 來訪問結構體成員進行輸入操作。

  • 結構體的輸出
  1. 逐個成員輸出(透過 printf 等函式)
    同樣可以使用 printf 等輸出函式對結構體的各個成員進行逐個輸出,示例如下:
#include <stdio.h>

struct Student {
    char name[50];
    int age;
    float score;
};

int main() {
    struct Student s = {"David", 23, 95.0};
    printf("學生姓名:%s\n", s.name);
    printf("學生年齡:%d\n", s.age);
    printf("學生成績:%f\n", s.score);
    return 0;
}
  1. 自定義函式輸出(便於統一格式等)
    編寫一個函式來輸出結構體變數的資訊,使輸出格式更規範、統一,示例如下:
#include <stdio.h>

struct Student {
    char name[50];
    int age;
    float score;
};

void outputStudent(struct Student s) {
    printf("姓名:%s,年齡:%d,成績:%f\n", s.name, s.age, s.score);
}

int main() {
    struct Student s = {"Eve", 24, 98.0};
    outputStudent(s);
    return 0;
}

你大概懂了?做兩道小題試試

一.
alt text
😀結構體

#include <stdio.h>
typedef struct Book{
	char title[100];
	char author[50];
	float price;
}book;
void input(book* p){
	printf("please put your information");//字串必須用雙引號!
	scanf("%s %s %f",p ->title, p ->author, &p -> price);
}
//&p -> price//!!!!
//第一次寫成  p -> & price
// 但這是語法錯誤的,因為 -> 後面應該直接跟成員名,而不是取地址運算子 &。
void output(book* p){

	printf("%s %s %f",p ->title, p ->author, p ->price);
}
int main() {
	book *p, i;
//	*p = i ;這個不是正確的初始化
	p = &i;//!!!
	input(p);
	output(p);
    return 0;
}

😀結構體陣列

#define N 5
#include <stdio.h>
typedef struct Book{
	char title[100];
	char author[50];
	float price;
}book;
void input(book* p){
	printf("please put your information");
	scanf("%s %s %f",p ->title, p ->author, &p -> price);
}

void output(book* p){

	printf("%s %s %0.2f\n",p ->title, p ->author, p ->price);
}
int main() {
	book *p;
	book arr[N];
	p = arr;
	int i;
	for(i=0;i<3;i++){
		input(p + i);
		output(p + i);	
	}

    return 0;
}


😀直接用.訪問,不用函式

#define N 5
#include <stdio.h>
typedef struct Book{
	char title[100];
	char author[50];
	float price;
}book;

void output(book* p){

	printf("%s %s %0.2f\n",p ->title, p ->author, p ->price);
}
int main() {
	book *p;
	book arr[N];
	p = arr;
	int i;
	printf("please put your information");//字串必須用雙引號!
	for(i=0;i<3;i++){
		scanf("%s %s %f",arr[i].title, arr[i].author, &arr[i].price);	
		
	}
	for(i=0;i<3;i++){
		output(p + i);
	}
    return 0;
}


顯然,用函式指標傳參更方便捏
二.
alt text

#define N 3
#include<stdio.h>
typedef struct employee{
	int id;
	char name[50];
	char department[30];
	int salary;
}Employee;
inputEmployee(Employee *p){
	scanf("%d %s %s %d",&p -> id, p -> name, p -> department, &p ->salary);
}
outputEmployee(Employee *p){
	printf("%d %s %s %d",p -> id, p -> name, p -> department, p ->salary);
}
int main(){
	Employee a[N];
	Employee * p;
	int i;
	p = a;
	for(i=0;i<N;i++){
		inputEmployee(p + i);
	}
	for(i=0;i<N;i++){
		outputEmployee(p + i);
	}
	return 0;
}



在C語言中,name[50] 表示定義了一個名為 name 的 字元陣列,長度為50

字元陣列裡可以輸多少漢字字母標點符號?

  1. 英文字母:每個英文字母(例如 'a', 'b', 'c' 等)佔用1 個字元的位置。

  2. 漢字:如果使用的是單位元組編碼(如 ASCII,但這不適用於漢字),每個漢字將無法正確儲存。在多位元組編碼(如 UTF-8)中,一個漢字通常會佔用 3 到 4 個字元的位置。

  3. 標點符號:大多數標點符號(例如 '.', ',', '!' 等)每個佔用 1個字元的位置。

字元陣列和字串

語法
  1. 定義:
    char myArray[10]; char 陣列名[陣列長度];
    陣列長度是一個常量

    • 無輸入的初始化:

      • 可以在定義時對字元陣列進行初始化
        char myArray[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
      • 使用字串進行初始化:
        char myArray[] = "Hello";
        這裡,"Hello"字串末尾的空字元'\0'會自動新增
    • 無初始化的輸入

      1. scanf函式或其變體來輸入字元陣列:
        scanf("%s", myArray);
        但要注意,%s格式化字串無法讀取 空白字元(空格、製表符或換行符),並且不會檢查陣列邊界,這可能導致緩衝區溢位。因此,更安全的做法是限制讀取的字元數:
        scanf("%9s", myArray); // 讀取最多9個字元,留一個位置給'\0'
        2.【推薦使用】 fgets(陣列,sizeof(陣列),stdin);處理scanf不可以解決的空格【空白字元輸入問題】

p.s.由於 gets 的不安全性,現代 C 程式應該避免使用它,而應該使用 fgets 或其他更安全的字串讀取函式。

#include <stdio.h>
int main() {
    char buffer[100]; // 建立一個足夠大的字元陣列來儲存讀取的行
    // 從標準輸入讀取一行,最多讀取 99 個字元(最後一個位置留給 '\0')
    fgets(buffer, sizeof(buffer), stdin);
    // 輸出讀取的內容
    printf("You entered: %s", buffer);
    return 0;
}
  1. 輸出
    • 按照字串形式輸出:
      printf("%s\n", myArray);
    • 按照字元陣列格式
      for(i=0;i<5;i++){ printf("%c",myArray[i]); }

字串 === last-child是\0的字元陣列

字串和字元陣列的不同運用場景

存在即合理

  1. 字元陣列的使用場景
    • 😀儲存固定長度或長度受限的文字序列
      • 當你確切知道要儲存的文字長度時,字元陣列是很好的選擇。例如,儲存日期格式“YYYY - MM - DD”,其長度固定為10(包括分隔符和結尾的空字元)。
      char date[11] = "2024-01-01";
      
      • 或者儲存一個人的身份證號碼,長度一般為18位,也可以使用字元陣列。
    • 對記憶體使用有嚴格控制的情況
      • 如果你的程式執行在資源受限的環境中,需要精確控制記憶體分配,字元陣列很有用。你可以提前計算出所需的字元陣列大小,避免不必要的記憶體浪費。例如,在嵌入式系統中儲存裝置配置資訊,如裝置名稱(假設最長10個字元)和IP地址(最長15個字元)。
      char deviceName[11];
      char ipAddress[16];
      
      • 這樣可以確保每個字元陣列只佔用剛好足夠的記憶體空間。
    • 😀需要對字元逐個處理的情況
      • 當你需要對每個字元進行單獨的操作,如加密、解密演算法,或者統計字元出現的頻率等操作時,字元陣列更方便。例如,對一個字元陣列中的字元進行簡單的加密,將每個字元的ASCII值加1。
      char message[] = "hello";
      for (int i = 0; i < strlen(message); i++) {
          message[i] = message[i] + 1;
      }
      printf("%s", message); 
      
  2. 字串的使用場景
    • 動態文字內容的儲存和操作
      • 當文字內容的長度不確定,且可能會動態變化時,字串更合適。例如,在文字編輯器程式中,使用者輸入的文字長度是不確定的。C語言中可以使用動態分配記憶體的字串來處理這種情況。
      char *text = (char *)malloc(100 * sizeof(char));
      // 可以根據使用者輸入動態擴充套件記憶體
      
      • 這裡的text可以根據實際輸入情況,透過realloc等函式來動態擴充套件記憶體,以適應不斷變化的文字長度。
    • 😀作為函式引數和返回值傳遞文字資訊
      • 很多函式需要傳遞文字資訊,使用字串作為引數更加方便和直觀。例如,在檔案讀取函式fgets中,它接受一個字串(字元陣列)作為引數來儲存讀取到的行。
      char line[100];
      fgets(line, sizeof(line), stdin);
      
      • 同樣,函式返回值也可以是字串。例如,一個函式返回一個錯誤訊息字串,方便呼叫者獲取並處理錯誤資訊。
    • 😀與字串處理庫函式配合使用
      • C語言有許多強大的字串處理函式,如strcpystrcatstrcmp等。當你需要進行字串的複製、拼接、比較等操作時,使用字串(字元陣列)作為這些函式的操作物件是很自然的選擇。
      char str1[50] = "hello";
      char str2[50] = " world";
      strcat(str1, str2);
      printf("%s", str1); 
      
四大字串函式和運用場景

在 C 語言中,標準庫 <string.h> 提供了許多用於處理字串的函式。
strlen(s) strcpy(s1,s2) strcmp(s1,s2) strcat(s1,s2)

1. 字串長度
❤strlen(s): 返回字串 s 的長度,不包括結尾的空字元 \0。
運用場景:確定字串中字元的數量,或者在分配記憶體之前檢查字串的長度。

2. 字串複製
❤ strcpy(s1, s2): 將字串 s2 複製到字串 s1。
運用場景:在需要建立字串副本時使用。

strncpy(s1, s2, n): 將字串 s2 的前 n 個字元複製到字串 s1。
運用場景:當需要限制複製的字元數量時,比如防止緩衝區溢位。

3. 字串連線
❤strcat(s1, s2): 將字串 s2 連線到字串 s1 的末尾。
運用場景:當需要將兩個字串合併為一個時。

strncat(s1, s2, n): 將字串 s2 的前 n 個字元連線到字串 s1 的末尾。
運用場景:當需要限制連線的字元數量時。

4. 字串比較
❤strcmp(s1, s2): 比較字串 s1 和 s2。
運用場景:在需要對字串進行排序或查詢時。

strncmp(s1, s2, n): 比較 s1 和 s2 的前 n 個字元。
運用場景:當只需要比較字串的前幾個字元時。

5.字串查詢
strstr(s1, s2): 在字串 s1 中查詢子字串 s2。
運用場景:當需要在字串中查詢特定的子串時。

strchr(s, c): 在字串 s 中查詢字元 c 的第一次出現。
運用場景:查詢特定字元的位置。

strrchr(s, c): 在字串 s 中查詢字元 c 的最後一次出現。
運用場景:從字串末尾開始查詢特定字元的位置。
字串其他操作

strspn(s1, s2): 返回 s1 中由 s2 中任意字元組成的初始子串的長度。
運用場景:用於查詢字串中的前導字元。

strcspn(s1, s2): 返回 s1 中由不在 s2 中的任意字元組成的初始子串的長度。
運用場景:用於查詢字串中第一個不符合條件的字元的位置。

strpbrk(s1, s2): 在字串 s1 中查詢 s2 中任何一個字元的第一次出現。
運用場景:查詢字串中任意一個字元的位置。

strtok(s1, s2): 將字串 s1 拆分為一系列由 s2 中任意字元分隔的標記。
運用場景:用於字串分割,比如解析命令列引數。


#include <stdio.h>
#include <string.h>
int main() {
    char source[] = "Hello, World!";
    char dest[20]; // 確保這個陣列足夠大,可以容納 source 字串
    // 複製字串
    strcpy(dest, source);
    // 輸出複製的字串及其長度
    printf("Copied string: %s\n", dest);
    printf("Length of copied string: %lu\n", strlen(dest));
    return 0;
}

p.s.在C語言程式設計中,以下是一些可能會使用 strcpy 函式的具體題目:

  • 字串複製:
    編寫一個程式,將使用者輸入的字串複製到一個新的字串變數中。
  • 字串處理函式:
    實現一個函式,該函式接收兩個字串引數,並返回第一個字串的副本,但所有小寫字母都轉換為大寫。
  • 字串拼接:
    編寫一個程式,將兩個字串拼接成一個新字串,並確保結果字串不會溢位。
  • 資料結構實現:
    在實現連結串列、樹或其他資料結構時,可能需要複製字串作為節點資料。

題目1:字串複製
編寫一個C程式,它要求使用者輸入一個字串,然後使用 strcpy 函式將這個字串複製到一個新的字串變數中,並列印出複製的字串。

#include<stdio.h>
#include<string.h>
int main(){
	char s[20] = "hellow,tom";
	char new[20];
	strcpy(new,s);
	printf("%s",new);
	return 0;
}

題目2:字串處理函式
編寫一個函式 toUpperCase,它接收一個字串並返回一個新的字串,其中所有小寫字母都被轉換為大寫。

#include<stdio.h>
#include<string.h>
void toUpperCase(char s[20]){//void!!!
//char function,返回的是一個字元,不可以返回字元陣列
	int i;
	i = 0;
	while(s[i] != '\0'){
		if(s[i] >= 97 && s[i] <= 122){
			s[i] = s[i] - 32;
		}
		i ++;//寫while不要忘記i ++!!!!
	}

}
int main(){
	char s[20] = "hellow,tom";
//	toUpperCase(s[20]);//錯誤的,代表傳遞第21個子級
	toUpperCase(s);//傳遞s就可以了
	printf("%s",s);
	return 0;
}

//在 ASCII 碼(美國資訊交換標準程式碼)中,大寫字母A - Z的編碼值是從 65 到 90,小寫字母a - z的編碼值是從 97 到 122。
//可以發現,同一個字母的大寫和小寫形式的 ASCII 碼值相差 32

在這些題目中,strcpy 用於建立字串的副本,以便在不改變原始字串的情況下進行操作。在使用 strcpy 時,務必確保目標緩衝區足夠大,以避免緩衝區溢位。

在C語言中,避免引數傳遞錯誤可以從以下幾個方面入手:

1. 理解函式引數的型別和要求
  • 仔細檢視函式定義:在呼叫函式之前,務必仔細檢視函式的宣告或定義,明確每個引數的型別、是傳值還是傳指標(引用)以及期望的引數值範圍等資訊。
    例如,如果函式定義為void func(int *ptr),那就清楚它需要傳遞一個指向int型別的指標。
  • 區分值傳遞和指標(引用)傳遞
    • 值傳遞:對於基本資料型別(如intfloatchar等)的引數,預設是值傳遞。這意味著函式內部會建立一個引數的副本進行操作,不會影響原始變數的值。例如:
    void increment(int num) {
       num++;
    }
    int main() {
       int a = 5;
       increment(a);
       printf("%d\n", a); // 輸出仍然是5,因為函式內部操作的是副本
       return 0;
    }
    
    • 指標(引用)傳遞:如果要在函式內部修改原始變數的值,或者傳遞大型資料結構(如陣列、結構體等)以避免複製開銷,通常使用指標(或引用,C++概念,C語言中類似透過指標實現)傳遞。例如:
    void increment_ptr(int *num_ptr) {
       (*num_ptr)++;
    }
    int main() {
       int a = 5;
       increment_ptr(&a);
       printf("%d\n", a); // 輸出是6,因為函式內部透過指標修改了原始變數的值
       return 0;
    }
    
  • 注意函式引數的求值順序(如果不確定):在某些複雜的表示式作為函式引數時,要注意引數的求值順序可能因編譯器而異。例如,在func(a++, b++)這樣的呼叫中,不同編譯器可能先計算a++還是b++,為了避免不確定性,儘量避免在一個函式呼叫中使用多個有副作用的表示式作為引數。
2. 正確傳遞陣列和指標引數
  • 陣列作為引數傳遞
    • 當陣列作為函式引數時,陣列名會退化為指向陣列首元素的指標。所以在傳遞陣列時,應該傳遞陣列名。例如:
    void print_array(int *arr, int size) {
       for (int i = 0; i < size; i++) {
          printf("%d ", arr[i]);
       }
    }
    int main() {
       int a[] = {1, 2, 3, 4, 5};
       print_array(a, sizeof(a)/sizeof(a[0]));
       return 0;
    }
    
    • 要注意在函式內部不能再使用sizeof來獲取陣列的真實大小(因為此時它只是一個指標),應該將陣列大小作為另一個引數傳遞給函式
  • 指標引數的合法性檢查
    • 在傳遞指標之前,確保指標是有效的。如果指標可能為NULL,在函式內部應該先進行檢查。例如:
    void process_data(int *data) {
       if (data == NULL) {
          printf("無效的指標\n");
          return;
       }
       // 正常處理資料
    }
    
    • 同時,要確保指標指向的記憶體區域是合法的、已經正確分配和初始化的。比如,不能傳遞一個指向已經釋放的記憶體區域或者未初始化的指標給函式,除非函式內部有專門的機制來處理這種情況。
3. 結構體和其他複雜資料型別作為引數
  • 結構體傳遞方式選擇
    • 可以選擇傳遞結構體本身(值傳遞),但這樣會產生複製開銷。對於大型結構體,這可能會影響效能。例如:
    struct Point {
       int x;
       int y;
    };
    void modify_point(struct Point p) {
       p.x++;
       p.y++;
    }
    int main() {
       struct Point p = {1, 2};
       modify_point(p);
       printf("(%d, %d)\n", p.x, p.y); // 輸出仍然是(1, 2),因為是值傳遞
       return 0;
    }
    
    • 更常用的是傳遞結構體指標,這樣可以在函式內部修改結構體的成員。例如:
    void modify_point_ptr(struct Point *p) {
       p->x++;
       p->y++;
    }
    int main() {
       struct Point p = {1, 2};
       modify_point_ptr(&p);
       printf("(%d, %d)\n", p.x, p.y); // 輸出是(2, 3),因為透過指標修改了結構體成員
       return 0;
    }
    
  • 對於包含指標成員的結構體:如果結構體包含指標成員,要確保指標成員指向的記憶體是有效的。在傳遞這樣的結構體時,可能需要在函式內部對指標成員指向的記憶體區域進行額外的操作和檢查,比如動態記憶體分配或釋放操作。

傳遞陣列名和元素個數(最常見的指標方式)!!!

相關文章