函式指標
函式指標是指向函式的指標變數,
通常我們說的指標變數是指向一個整型、字符型或陣列等變數,而函式指標是指向函式,
函式指標可以像一般函式一樣,用于呼叫函式、傳遞引數,
函式指標變數的宣告:
// 宣告一個指向同樣引數、回傳值的函式指標型別
typedef int (*fun_ptr)(int,int);
以下實體宣告了函式指標變數 p,指向函式 max:
#include <stdio.h>
int max(int x, int y)
{
return x > y ? x : y;
}
int main(void)
{
/* p 是函式指標 */
int (* p)(int, int) = & max; // &可以省略
int a, b, c, d;
printf("請輸入三個數字:");
scanf("%d %d %d", & a, & b, & c);
/* 與直接呼叫函式等價,d = max(max(a, b), c) */
d = p(p(a, b), c);
printf("最大的數字是: %d\n", d);
return 0;
}
編譯執行,輸出結果如下:
請輸入三個數字:1 2 3
最大的數字是: 3
回呼函式
函式指標作為某個函式的引數
函式指標變數可以作為某個函式的引數來使用的,回呼函式就是一個通過函式指標呼叫的函式,
簡單講:回呼函式是由別人的函式執行時呼叫你實作的函式,
以下是來自知乎作者常溪玲的解說:
你到一個商店買東西,剛好你要的東西沒有貨,于是你在店員那里留下了你的電話,過了幾天店里有貨了,店員就打了你的電話,然后你接到電話后就到店里去取了貨,在這個例子里,你的電話號碼就叫回呼函式,你把電話留給店員就叫登記回呼函式,店里后來有貨了叫做觸發了回呼關聯的事件,店員給你打電話叫做呼叫回呼函式,你到店里去取貨叫做回應回呼事件,
實體
實體中 populate_array 函式定義了三個引數,其中第三個引數是函式的指標,通過該函式來設定陣列的值,
實體中我們定義了回呼函式 getNextRandomValue,它回傳一個隨機值,它作為一個函式指標傳遞給 populate_array 函式,
populate_array 將呼叫 10 次回呼函式,并將回呼函式的回傳值賦值給陣列,
注意:size_t 是一種資料型別,近似于無符號整型,但容量范圍一般大于 int 和 unsigned,這里使用 size_t 是為了保證 arraysize 變數能夠有足夠大的容量來儲存可能大的陣列個數值,
#include <stdlib.h>
#include <stdio.h>
// 回呼函式
void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
for (size_t i=0; i<arraySize; i++)
array[i] = getNextValue();
}
// 獲取隨機值
int getNextRandomValue(void)
{
return rand();
}
int main(void)
{
int myarray[10];
populate_array(myarray, 10, getNextRandomValue);
for(int i = 0; i < 10; i++) {
printf("%d ", myarray[i]);
}
printf("\n");
return 0;
}
編譯執行,輸出結果如下:
16807 282475249 1622650073 984943658 1144108930 470211272 101027544 1457850878 1458777923 2007237709
字串
在 C 語言中,字串實際上是使用 null 字符 '\0' 終止的一維字符陣列,因此,一個以 null 結尾的字串,包含了組成字串的字符,
下面的宣告和初始化創建了一個 "Hello" 字串,由于在陣列的末尾存盤了空字符,所以字符陣列的大小比單詞 "Hello" 的字符數多一個,
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
依據陣列初始化規則,您可以把上面的陳述句寫成以下陳述句:
char greeting[] = "Hello";
以下是 C/C++ 中定義的字串的記憶體表示:

其實,不需要把 null 字符放在字串常量的末尾,C 編譯器會在初始化陣列時,自動把 '\0' 放在字串的末尾,讓我們嘗試輸出上面的字串:
#include <stdio.h>
int main ()
{
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
printf("Greeting message: %s\n", greeting );
return 0;
}
當上面的代碼被編譯和執行時,它會產生下列結果:
Greeting message: Hello
操作字串的函式
| 序號 | 函式 & 目的 |
|---|---|
| 1 | strcpy(s1, s2); 復制字串 s2 到字串 s1, |
| 2 | strcat(s1, s2); 連接字串 s2 到字串 s1 的末尾, |
| 3 | strlen(s1); 回傳字串 s1 的長度, |
| 4 | strcmp(s1, s2); 如果 s1 和 s2 是相同的,則回傳 0;如果 s1<s2 則回傳小于 0;如果 s1>s2 則回傳大于 0, |
| 5 | strchr(s1, ch); 回傳一個指標,指向字串 s1 中字符 ch 的第一次出現的位置, |
| 6 | strstr(s1, s2); 回傳一個指標,指向字串 s1 中字串 s2 的第一次出現的位置, |
下面的實體使用了上述的一些函式:
#include <stdio.h>
#include <string.h>
int main ()
{
char str1[12] = "Hello";
char str2[12] = "World";
char str3[12];
int len ;
/* 復制 str1 到 str3 */
strcpy(str3, str1);
printf("strcpy( str3, str1) : %s\n", str3 );
/* 連接 str1 和 str2 */
strcat( str1, str2);
printf("strcat( str1, str2): %s\n", str1 );
/* 連接后,str1 的總長度 */
len = strlen(str1);
printf("strlen(str1) : %d\n", len );
return 0;
}
當上面的代碼被編譯和執行時,它會產生下列結果:
strcpy( str3, str1) : Hello
strcat( str1, str2): HelloWorld
strlen(str1) : 10
C 標準庫中還有更多字串相關的函式,
結構體
結構是 C 編程中一種用戶自定義的可用的資料型別,它允許記憶體儲不同型別的資料項,結構用于表示一條記錄,
定義結構
為了定義結構,您必須使用 struct 陳述句,struct 陳述句定義了一個包含多個成員的新的資料型別,struct 陳述句的格式如下:
struct tag {
member-list
member-list
member-list
...
} variable-list ;
tag 是結構體標簽,
member-list 是標準的變數定義,比如 int i; 或者 float f,或者其他有效的變數定義,
variable-list 結構變數,定義在結構的末尾,最后一個分號之前,您可以指定一個或多個結構變數,下面是宣告 Book 結構的方式:
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book;
在一般情況下,tag、member-list、variable-list 這 3 部分至少要出現 2 個,以下為實體:
//此宣告宣告了擁有3個成員的結構體,分別為整型的a,字符型的b和雙精度的c
//同時又宣告了結構體變數s1
//這個結構體并沒有標明其標簽
struct
{
int a;
char b;
double c;
} s1;
//此宣告宣告了擁有3個成員的結構體,分別為整型的a,字符型的b和雙精度的c
//結構體的標簽被命名為SIMPLE,沒有宣告變數
struct SIMPLE
{
int a;
char b;
double c;
};
//用SIMPLE標簽的結構體,另外宣告了變數t1、t2、t3
struct SIMPLE t1, t2[20], *t3;
//也可以用typedef創建新型別
typedef struct
{
int a;
char b;
double c;
} Simple2;
//現在可以用Simple2作為型別宣告新的結構體變數
Simple2 u1, u2[20], *u3;
在上面的宣告中,第一個和第二宣告被編譯器當作兩個完全不同的型別,即使他們的成員串列是一樣的,
結構體的成員可以包含其他結構體,也可以包含指向自己結構體型別的指標,而通常這種指標的應用是為了實作一些更高級的資料結構如鏈表和樹等,
//此結構體的宣告包含了其他的結構體
struct COMPLEX
{
char string[100];
struct SIMPLE a;
};
//此結構體的宣告包含了指向自己型別的指標
struct NODE
{
char string[100];
struct NODE *next_node;
};
如果兩個結構體互相包含,則需要對其中一個結構體進行不完整宣告,如下所示:
struct B; //對結構體B進行不完整宣告
//結構體A中包含指向結構體B的指標
struct A
{
struct B *partner;
//other members;
};
//結構體B中包含指向結構體A的指標,在A宣告完后,B也隨之進行宣告
struct B
{
struct A *partner;
//other members;
};
結構體變數的初始化
和其它型別變數一樣,對結構體變數可以在定義時指定初始值,
#include <stdio.h>
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book = {"C 語言", "RUNOOB", "編程語言", 123456};
int main()
{
printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book.title, book.author, book.subject, book.book_id);
}
執行輸出結果為:
title : C 語言
author: RUNOOB
subject: 編程語言
book_id: 123456
訪問結構成員
為了訪問結構的成員,我們使用成員訪問運算子(.),成員訪問運算子是結構變數名稱和我們要訪問的結構成員之間的一個句號,您可以使用 struct 關鍵字來定義結構型別的變數,下面的實體演示了結構的用法:
#include <stdio.h>
#include <string.h>
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
struct Books Book1; /* 宣告 Book1,型別為 Books */
struct Books Book2; /* 宣告 Book2,型別為 Books */
/* Book1 詳述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;
/* Book2 詳述 */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;
/* 輸出 Book1 資訊 */
printf( "Book 1 title : %s\n", Book1.title);
printf( "Book 1 author : %s\n", Book1.author);
printf( "Book 1 subject : %s\n", Book1.subject);
printf( "Book 1 book_id : %d\n", Book1.book_id);
/* 輸出 Book2 資訊 */
printf( "Book 2 title : %s\n", Book2.title);
printf( "Book 2 author : %s\n", Book2.author);
printf( "Book 2 subject : %s\n", Book2.subject);
printf( "Book 2 book_id : %d\n", Book2.book_id);
return 0;
}
當上面的代碼被編譯和執行時,它會產生下列結果:
Book 1 title : C Programming
Book 1 author : Nuha Ali
Book 1 subject : C Programming Tutorial
Book 1 book_id : 6495407Book 2 title : Telecom Billing
Book 2 author : Zara Ali
Book 2 subject : Telecom Billing Tutorial
Book 2 book_id : 6495700
結構作為函式引數
您可以把結構作為函式引數,傳參方式與其他型別的變數或指標類似,
#include <stdio.h>
#include <string.h>
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
/* 函式宣告 */
void printBook( struct Books book );
int main( )
{
struct Books Book1; /* 宣告 Book1,型別為 Books */
struct Books Book2; /* 宣告 Book2,型別為 Books */
/* Book1 詳述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;
/* Book2 詳述 */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;
/* 輸出 Book1 資訊 */
printBook( Book1 );
/* 輸出 Book2 資訊 */
printBook( Book2 );
return 0;
}
void printBook( struct Books book )
{
printf( "Book title : %s\n", book.title);
printf( "Book author : %s\n", book.author);
printf( "Book subject : %s\n", book.subject);
printf( "Book book_id : %d\n", book.book_id);
}
當上面的代碼被編譯和執行時,它會產生下列結果:
Book title : C Programming
Book author : Nuha Ali
Book subject : C Programming Tutorial
Book book_id : 6495407Book title : Telecom Billing
Book author : Zara Ali
Book subject : Telecom Billing Tutorial
Book book_id : 6495700
指向結構的指標
您可以定義指向結構的指標,方式與定義指向其他型別變數的指標相似,如下所示:
struct Books *struct_pointer;
現在,您可以在上述定義的指標變數中存盤結構變數的地址,為了查找結構變數的地址,請把 & 運算子放在結構名稱的前面,如下所示:
struct_pointer = &Book1;
為了使用指向該結構的指標訪問結構的成員,您必須使用 -> 運算子,如下所示:
struct_pointer->title;
讓我們使用結構指標來重寫上面的實體,這將有助于理解結構指標的概念:
#include <stdio.h>
#include <string.h>
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
/* 函式宣告 */
void printBook( struct Books *book );
int main( )
{
struct Books Book1; /* 宣告 Book1,型別為 Books */
struct Books Book2; /* 宣告 Book2,型別為 Books */
/* Book1 詳述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;
/* Book2 詳述 */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;
/* 通過傳 Book1 的地址來輸出 Book1 資訊 */
printBook( &Book1 );
/* 通過傳 Book2 的地址來輸出 Book2 資訊 */
printBook( &Book2 );
return 0;
}
void printBook( struct Books *book )
{
printf( "Book title : %s\n", book->title);
printf( "Book author : %s\n", book->author);
printf( "Book subject : %s\n", book->subject);
printf( "Book book_id : %d\n", book->book_id);
}
當上面的代碼被編譯和執行時,它會產生下列結果:
Book title : C Programming
Book author : Nuha Ali
Book subject : C Programming Tutorial
Book book_id : 6495407Book title : Telecom Billing
Book author : Zara Ali
Book subject : Telecom Billing Tutorial
Book book_id : 6495700
位域
有些資訊在存盤時,并不需要占用一個完整的位元組,而只需占幾個或一個二進制位,例如在存放一個開關量時,只有 0 和 1 兩種狀態,用 1 位二進位即可,為了節省存盤空間,并使處理簡便,C 語言又提供了一種資料結構,稱為"位域"或"位段",
所謂"位域"是把一個位元組中的二進位劃分為幾個不同的區域,并說明每個區域的位數,每個域有一個域名,允許在程式中按域名進行操作,這樣就可以把幾個不同的物件用一個位元組的二進制位域來表示,
典型的實體:
- 用 1 位二進位存放一個開關量時,只有 0 和 1 兩種狀態,
- 讀取外部檔案格式——可以讀取非標準的檔案格式,例如:9 位的整數,
位域的定義和位域變數的說明
位域定義與結構定義相仿,其形式為:
struct 位域結構名
{
位域串列
};
其中位域串列的形式為:
型別說明符 位域名: 位域長度
例如:
struct bs{
int a:8;
int b:2;
int c:6;
}data;
說明 data 為 bs 變數,共占兩個位元組,其中位域 a 占 8 位,位域 b 占 2 位,位域 c 占 6 位,
1位元組=1B=8個二進制位
讓我們再來看一個實體:
struct packed_struct {
unsigned int f1:1;
unsigned int f2:1;
unsigned int f3:1;
unsigned int f4:1;
unsigned int type:4;
unsigned int my_int:9;
} pack;
在這里,packed_struct 包含了 6 個成員:四個 1 位的識別符號 f1..f4、一個 4 位的 type 和一個 9 位的 my_int,
對于位域的定義尚有以下幾點說明:
-
一個位域存盤在同一個位元組中,如一個位元組所剩空間不夠存放另一位域時,則會從下一單元起存放該位域,也可以有意使某位域從下一單元開始,例如:
struct bs{ unsigned a:4; unsigned :4; /* 空域 */ unsigned b:4; /* 從下一單元開始存放 */ unsigned c:4 }在這個位域定義中,a 占第一位元組的 4 位,后 4 位填 0 表示不使用,b 從第二位元組開始,占用 4 位,c 占用 4 位,
-
由于位域不允許跨兩個位元組,因此位域的長度不能大于一個位元組的長度,也就是說不能超過8位二進位,如果最大長度大于計算機的整數字長,一些編譯器可能會允許域的記憶體重疊,另外一些編譯器可能會把大于一個域的部分存盤在下一個字中,
-
位域可以是無名位域,這時它只用來作填充或調整位置,無名的位域是不能使用的,例如:
struct k{ int a:1; int :2; /* 該 2 位不能使用 */ int b:3; int c:2; };
從以上分析可以看出,位域在本質上就是一種結構型別,不過其成員是按二進位分配的,
實體
如果程式的結構中包含多個開關量,只有 TRUE/FALSE 變數,如下:
struct
{
unsigned int widthValidated;
unsigned int heightValidated;
} status;
這種結構需要 8 位元組的記憶體空間,但在實際上,在每個變數中,我們只存盤 0 或 1,上面的結構可以重寫成:
struct
{
unsigned int widthValidated : 1;
unsigned int heightValidated : 1;
} status;
現在,上面的結構中,status 變數將占用 4 個位元組的記憶體空間,但是只有 2 位被用來存盤值,如果您用了 32 個變數,每一個變數寬度為 1 位,那么 status 結構將使用 4 個位元組,但只要您再多用一個變數,如果使用了 33 個變數,那么它將分配記憶體的下一段來存盤第 33 個變數,這個時候就開始使用 8 個位元組,讓我們看看下面的實體來理解這個概念:
#include <stdio.h>
#include <string.h>
/* 定義簡單的結構 */
struct
{
unsigned int widthValidated;
unsigned int heightValidated;
} status1;
/* 定義位域結構 */
struct
{
unsigned int widthValidated : 1;
unsigned int heightValidated : 1;
} status2;
int main( )
{
printf( "Memory size occupied by status1 : %d\n", sizeof(status1));
printf( "Memory size occupied by status2 : %d\n", sizeof(status2));
return 0;
}
當上面的代碼被編譯和執行時,它會產生下列結果:
Memory size occupied by status1 : 8
Memory size occupied by status2 : 4
位域宣告
在結構內宣告位域的形式如下:
struct
{
type [member_name] : width ;
};
下面是有關位域中變數元素的描述:
| 元素 | 描述 |
|---|---|
| type | 只能為 int(整型),unsigned int(無符號整型),signed int(有符號整型) 三種型別,決定了如何解釋位域的值, |
| member_name | 位域的名稱, |
| width | 位域中位的數量,寬度必須小于或等于指定型別的位寬度, |
帶有預定義寬度的變數被稱為位域,位域可以存盤多于 1 位的數,例如,需要一個變數來存盤從 0 到 7 的值,您可以定義一個寬度為 3 位(2進制位)的位域,如下:
struct
{
unsigned int age : 3;
} Age;
上面的結構定義指示 C 編譯器,age 變數將只使用 3 位來存盤這個值,如果您試圖使用超過 3 位,則無法完成,讓我們來看下面的實體:
#include <stdio.h>
#include <string.h>
struct
{
unsigned int age : 3;
} Age;
int main( )
{
Age.age = 4;
printf( "Sizeof( Age ) : %d\n", sizeof(Age) );
printf( "Age.age : %d\n", Age.age );
Age.age = 7;
printf( "Age.age : %d\n", Age.age );
Age.age = 8; // 二進制表示為 1000 有四位,超出
printf( "Age.age : %d\n", Age.age );
return 0;
}
當上面的代碼被編譯時,它會帶有警告,當上面的代碼被執行時,它會產生下列結果:
Sizeof( Age ) : 4
Age.age : 4
Age.age : 7
Age.age : 0
位域的使用
位域的使用和結構成員的使用相同,其一般形式為:
位域變數名.位域名
位域變數名->位域名
位域允許用各種格式輸出,
請看下面的實體:
#include <stdio.h>
int main(){
struct bs{
unsigned a:1;
unsigned b:3;
unsigned c:4;
} bit,*pbit;
bit.a=1; /* 給位域賦值(應注意賦值不能超過該位域的允許范圍) */
bit.b=7; /* 給位域賦值(應注意賦值不能超過該位域的允許范圍) */
bit.c=15; /* 給位域賦值(應注意賦值不能超過該位域的允許范圍) */
printf("%d,%d,%d\n",bit.a,bit.b,bit.c); /* 以整型量格式輸出三個域的內容 */
pbit=&bit; /* 把位域變數 bit 的地址送給指標變數 pbit */
pbit->a=0; /* 用指標方式給位域 a 重新賦值,賦為 0 */
pbit->b&=3; /* 使用了復合的位運算子 "&=",相當于:pbit->b=pbit->b&3,位域 b 中原有值為 7,與 3 作按位與運算的結果為 3(111&011=011,十進制值為 3) */
pbit->c|=1; /* 使用了復合位運算子"|=",相當于:pbit->c=pbit->c|1,其結果為 15 */
printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c); /* 用指標方式輸出了這三個域的值 */
return 0;
}
得到以下結果
1,7,15
0,3,15
上例程式中定義了位域結構 bs,三個位域為 a、b、c,說明了 bs 型別的變數 bit 和指向 bs 型別的指標變數 pbit,這表示位域也是可以使用指標的,
參考自:https://www.runoob.com/cprogramming/c-tutorial.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/56854.html
標籤:C
