高效c語言2對象、函數和類型

来源:https://www.cnblogs.com/testing-/archive/2023/05/12/17390682.html
-Advertisement-
Play Games

​ 去年年底的因為業務需要需要在使用tk.mybaits框架的系統中實現指定欄位的更新,可是tk.mybaits框架本身並不支持這個功能,我翻遍了CSDN和其他相關的技術相關的網站都沒有找到相關的解決方法。於是我通過幾天的翻閱相關資料和摸索後終於實現了這個功能。最近事情不是很多,想到又想到了去年解決 ...


本章中,你將學習對象、函數和類型。我們將研究如何聲明變數(有標識符的對象)和函數,獲取對象的地址,並對這些對象指針的解引用。你已經看到了C語言程式員可用的一些類型, C語言中的類型不是對象就是函數。

對象、函數、類型和指針

對象是你可以表示數值的存儲。準確地說,C標準(ISO/IEC 9899:2018)將對象定義為 "執行環境中的數據存儲區域,其內容可以代表數值",並補充說明,"當被引用時,對象可以被解釋為具有特定類型"。變數是對象的例子。

變數會聲明的類型,告訴你它的值代表哪種對象。例如類型為int的對象包含整數值。類型很重要,因為代表一種類型的對象的比特集合,如果被解釋為不同類型的對象,可能會有不同的值。例如,數字1在IEEE 754(IEEE浮點運算標準)中由比特模式0x3f800000(IEEE 754-2008)表示。但是,如果你把這個相同的比特模式解釋為一個整數,你會得到1,065,353,216的值,而不是1。
函數不是對象,但確實有類型。

C語言也有指針,它在地址--記憶體中存儲對象或函數的位置。指針類型是由引用類型的函數或對象類型派生出來的。從被引用類型T派生出來的指針類型被稱為對T的指針。

聲明變數

聲明變數時,需要指定類型,並提供名稱用來引用該變數。

可以一行聲明多個變數,但如果變數是指針或數組,或者變數是不同的類型,這樣做就會引起混亂。下麵的聲明都是正確的。

char *src, c;
int x, y[5];
int m[12], n[15][3], o[21];

第一行聲明瞭兩個變數,src和c,它們的類型不同。src變數的類型為char *,而c的類型為char。第二行再次聲明瞭兩個類型不同的變數,x和y。變數x的類型是int,y是由五個元素組成的數組,類型是int。第三行聲明瞭三個數組-m、n和o-具有不同的尺寸和元素數。

一行一種類型的聲明可讀性會更好:

char *src;    // src has a type of char *
char c;       // c has a type of char
int x;        // x has a type int
int y[5];     // y is an array of 5 elements of type int
int m[12];    // m is an array of 12 elements of type int
int n[15][3]; // n is an array of 15 arrays of 3 elements of type int
int o[21];    // o is an array of 21 elements of type int

實例:交換值1

在{ }字元之間有代碼塊稱為複合語句。我們在主函數中定義了兩個變數,a和b。我們聲明這些變數的類型為int,並將它們分別初始化為21和17。每個變數都必須有一個聲明。然後主函數調用swap函數來嘗試交換這兩個整數的值。

#include <stdio.h>

void swap(int a, int b) {
  	int t = a; 
  	a = b;
  	b = t;
  	printf("swap: a = %d, b = %d\n", a, b);
}

int main(void) {
    int a = 21;
    int b = 17;
    swap(a, b); 
    printf("main: a = %d, b = %d\n", a, b);
    return 0;
}

局部變數,如清單2-1中的a和b,具有自動存儲期限,這意味著它們一直存在,直到執行離開它們被定義的塊。我們將嘗試交換存儲在這兩個變數中的值。

swap函數聲明瞭兩個參數,a和b,你用它們來向這個函數傳遞參數。我們還在交換函數中聲明瞭int類型的臨時變數t,並將其初始化為a的值。這個變數用於臨時保存a中存儲的值,以便在交換過程中不會丟失。

執行結果

$ ./a.out
swap: a = 17, b = 21
main: a = 21, b = 17

變數a和b分別被初始化為21和17。在swap函數中對printf的第一次調用顯示這兩個值被交換了,但在main中對printf的第二次調用顯示原始值沒有變化。

C語言是傳值的語言,傳參時參數的值被覆制到一個單獨的變數中,以便在函數中使用。swap函數將你作為參數傳遞的對象的值分配給各自的參數。當函數中的參數值發生變化時,調用方的值不會受到影響,因為它們是不同的對象。因此,在第二次調用printf時,變數a和b在main中保留了它們的原始值。

實例:交換值2

我們使用指示符(*)來聲明指針

#include <stdio.h>

int swap(int *_a, int *_b) {
	int tmp = *_a;
	*_a = *_b;
	*_b = tmp;
}

int main(void) {
	int a = 21;
	int b = 17;
	swap(&a, &b);
	printf("a = %d, b = %d\n", a, b);
	return 0;
}

清單2-3:修改後的使用指針的交換函數

當在函數聲明或定義中使用時,作為指針聲明器的一部分,表示參數是指向特定類型的對象或函數的指針。註意_a表示指針,_a表示指針指向的值。&獲取操作符的地址。

執行結果

$ ./a.out
a = 17, b = 21

變數a和b分別被初始化為21和17。然後代碼將這些對象的地址作為參數傳遞給交換函數。

在swap函數中,參數_a和_b現在都被聲明為int的指針類型,並且包含了從調用函數(在本例中,main)傳遞給swap的參數的副本。這些地址副本仍然指向完全相同的對象,所以當它們所引用的對象的值在交換函數中被交換時,在main中聲明的原始對象的內容也被訪問並被交換。這種方法通過生成對象地址,通過值傳遞這些地址,然後通過地址來訪問原始對象,即傳址。

範圍

對象、函數、巨集和其他C語言標識符都有範圍,它限定了它們可以被訪問的連續區域。C語言有四種類型的範圍:文件、塊、函數原型和函數。

對象或函數標識符的範圍是由它的聲明位置決定的。如果聲明在任何塊或參數列表之外為文件範圍;如果聲明出現在塊內或參數內,只能在該塊內訪問。

如果聲明出現在函數原型的參數聲明列表中(不是函數定義的一部分),那麼標識符具有函數原型作用域,它終止於函數聲明末端; 函數範圍是指函數定義的開頭{和結尾}之間的區域。標簽名是唯一一種函數作用域的標識符。標簽是標識符,後面有一個冒號,用來標識函數中的一個語句,控制權可以被轉移到這個語句中。

作用域可以被嵌套,有內部和外部作用域。內層作用域可以訪問外層作用域,但反之不行。如果你在內層作用域和外層作用域中都聲明瞭同一個標識符,那麼在外層作用域中聲明的標識符會被內層作用域中的標識符所隱藏,後者具有優先權。

存儲期限

有四種存儲期限可供選擇:自動、靜態、線程和分配。你已經看到,自動存儲期限的對象被聲明在塊中或作為函數參數。這些對象的生命周期從它們被聲明的塊開始執行時開始,到塊的執行結束時結束。

範圍和壽命是完全不同的概念。範圍適用於標識符,而壽命適用於對象。標識符的範圍是代碼區域,在這個區域中,標識符所表示的對象可以通過它的名字被訪問。對象的生命周期是該對象存在的時間段。

在文件作用域中聲明的對象具有靜態存儲期限。這些對象的生命期是程式的整個執行過程,它們的存儲值在程式啟動前被初始化。你也可以通過使用存儲類指定符static,將塊作用域中的變數聲明為具有靜態存儲期限,如清單2-6中的計數例子所示。這些對象在函數退出後持續存在。

#include <stdio.h>

void increment(void) {
  static unsigned int counter = 0;
  counter++;
  printf("%d ", counter);
}

int main(void) {
  for (int i = 0; i < 5; i++) {
    increment();
  }
  return 0;
}

這個程式輸出1 2 3 4 5。我們在程式啟動時將靜態變數counter初始化為0,併在每次調用increment函數時將其遞增。計數器的生命周期是程式的整個執行過程,它將在整個生命周期內保留其最後存儲的值。你可以通過用文件範圍聲明計數器來實現同樣的行為。然而在可能的情況下,限制對象的範圍是一種良好的軟體工程實踐。

靜態對象必須用常量值而不是變數來初始化。

int *func(int i) {
  const int j = i; // ok
  static int k = j; // error
  return &k;
}

常量值指的是字面常量(例如,1、'a'或0xFF)、枚舉成員以及alignof或sizeof等運算符的結果。

線程存儲持續時間用於併發編程,動態分配的記憶體。

對齊

對象類型有對齊要求,對象可能被分配的地址進行限制。對齊代表了給定對象可以被分配的連續地址之間的位元組數。CPU在訪問對齊的數據(例如,數據地址是數據大小的倍數)和未對齊的數據時可能有不同的行為。

一些機器指令可以在非字的邊界上執行多位元組訪問,但可能會有性能上的損失。字是自然的、固定大小的數據單位,由指令集或處理器的硬體處理。一些平臺不能訪問未對齊的記憶體。對齊要求可能取決於CPU字的大小(通常為16、32或64位)。

一般來說,C語言程式員不需要關心對齊要求,因為編譯器為其各種類型選擇合適的對齊方式。對於所有的標準類型,包括數組和結構,從malloc動態分配的記憶體都需要充分對齊。然而,在極少數情況下,你可能需要覆蓋編譯器的預設選擇;例如,在必須從二冪地址邊界開始的記憶體緩存行邊界上對齊數據,或者滿足其他系統特定的要求。傳統上,這些要求是通過linker命令來滿足的,或者通過malloc對記憶體進行整體分配,然後將用戶地址向上舍入,或者通過涉及其他非標準設施的類似操作。

C11引入了一種簡單的、向前相容的機制來指定對齊方式。對齊是以size_t類型的值表示的。每個有效的對齊值都是的2整數次方。每個對象都有預設的對齊要求:更嚴格的對齊(更大的2次方)可以通過對齊指定器(_Alignas)來請求。你可以在聲明的指定器中包個對齊方式的指定器。清單2-7使用對齊指定符來確保good_buff是正確對齊的(bad_buff對於成員訪問表達式可能有不正確的對齊)。

struct S {
  int i; double d; char c;
};

int main(void) {
  unsigned char bad_buff[sizeof(struct S)];
  _Alignas(struct S) unsigned char good_buff[sizeof(struct S)];

  struct S *bad_s_ptr = (struct S *)bad_buff;   // wrong pointer alignment
  struct S *good_s_ptr = (struct S *)good_buff; // correct pointer alignment
}

對齊是按從弱到強(也叫嚴格)的順序排列的。

對象類型

我們將介紹布爾類型、字元類型和數字類型(包括整數和浮點類型)。

布爾類型

聲明為_Bool的對象只能存儲0和1的值。這種布爾類型是在C99中引入的,並以下劃線開始,以便在已經聲明瞭自己的標識符名為bool或boolean的現有程式中加以區分。以下劃線和大寫字母或另一個下劃線開頭的標識符總是被保留。

如果你包含頭文件<stdbool.h>,你也可以把這個類型拼成bool,並給它賦值為true(擴展為整數常數1)和false(擴展為整數常數0)。在這裡,我們使用兩種類型名稱的拼寫來聲明兩個布爾變數:

#include <stdbool.h>
_Bool flag1 = 0;
bool flag2 = false;

兩種拼法都可以使用,但最好使用bool,因為這是語言的長期方向。

字元類型

C語言定義了三種字元類型:char、signed char和unsigned char。每個編譯器的實現都會將char定義為具有相同的對齊方式、大小、範圍、表示方式和行為,即signed char或nsigned char。無論做出什麼樣的選擇,char都是獨立的類型,與其他兩種類型都不相容。

char類型通常用於表示C語言程式中的字元數據。特別是,char類型的對象必須能夠表示執行環境中所需要的最小字元集(稱為基本執行字元集),包括大寫和小寫字母、10位小數、空格字元以及各種標點符號和控制字元。char類型不適合整數數據;使用signed char來表示小的有符號整數值,使用unsigned char來表示小的無符號值,是比較安全的。

基本的執行字元集適合許多傳統的數據處理應用的需要,但它缺乏非英文字母是國際用戶接受的障礙。為瞭解決這一需要,C標準委員會指定了一種新的、寬的類型,以允許大型字元集。你可以通過使用wchar_t類型將大字元集的字元表示為寬字元,它通常比基本字元占用更多空間。通常情況下,實現者選擇16或32位來表示一個寬字元。C標準庫提供了同時支持窄字元和寬字元類型的函數。

數值類型

C提供了幾種數字類型,可以用來表示整數、枚舉器和浮點值。第3章更詳細地介紹了其中的一些類型,但這裡是簡單的介紹。

  • 整數類型

有符號的整數類型可以用來表示負數、正數和零。有符號的整數類型包括signed char、short int、int、long int和long long int。
除了int本身,在這些類型的聲明中可以省略關鍵字int,所以你可以,例如,用long long而不是long long int來聲明一個類型。
對於每個有符號的整數類型,都有相應的無符號整數類型,使用相同的存儲量:unsigned char、unsigned short int、unsigned int、unsigned long int和unsigned long long int。無符號類型只能用來表示正數和零。

有符號和無符號整數類型用於表示各種大小的整數。每個平臺(當前或歷史)都決定了這些類型的大小,給定了一些約束條件。每個類型都有最小的可表示範圍。這些類型按寬度排序,保證較寬的類型至少和較窄的類型一樣大,這樣,long long int類型的對象可以表示long int類型的對象可以表示的所有值,long int類型的對象可以表示int類型的對象可以表示的所有值,等等。各種整數類型的實際大小可以從<limits.h>頭文件中指定的各種整數類型的最小和最大可表示值推斷出來。

int類型通常具有執行環境的架構所建議的自然大小,因此在16位架構上,其大小為16位寬,在32位架構上為32位寬。你可以通過使用<stdint.h>或<inttypes.h>頭文件的類型定義來指定實際寬度的整數,如uint32_t。這些頭文件還為最寬的可用整數類型提供了類型定義:uintmax_t和intmax_t。
第3章詳細介紹了整數類型。

  • 枚舉類型

枚舉,或稱enum,允許你定義一個類型,在具有可枚舉的常量值集的情況下,為整數值分配名稱(枚舉器)。下麵是枚舉的例子:

enum day { sun, mon, tue, wed, thu, fri, sat }; 
enum cardinal_points { north = 0, east = 90, south = 180, west = 270 };
enum months { jan = 1, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec };

如果你沒有用=操作符給第一個枚舉器指定值,那麼它的枚舉常量的值就是0,而後面每個沒有=的枚舉器都會在前面的枚舉常量的值上加1。因此,day枚舉中sun的值是0,mon是1,以此類推。
你也可以給每個枚舉器分配特定的值,如cardinal_points枚舉所示。與枚舉器一起使用=可能會產生具有重覆值的枚舉常數,如果你錯誤地認為所有的值都是唯一的,這可能是一個問題。months枚舉將第一個枚舉器設置為1,每個後續的枚舉器如果沒有被特別指定一個值,將被遞增1。
枚舉常量的實際值必須可以作為int來表示,但是它的類型是由實現定義的。例如,Visual C++使用有符號的int,而GCC使用無符號的int。

  • 浮點類型

C語言支持三種浮點類型:float、double和long double。浮點運算類似於實數運算,並經常被用作實數運算的模型。C語言支持多種浮點表示法,包括在大多數系統上支持IEEE浮點算術標準(IEEE 754-2008)。浮點表示法的選擇取決於實現。第3章詳細介紹了浮點類型。

  • void類型
    關鍵字void(本身)的意思是 "不能容納任何值"。例如,你可以用它來表示函數不返回值,或者作為函數的唯一參數來表示該函數不接受參數。另一方面,派生類型void *意味著指針可以引用任何對象。我將在本章後面討論派生類型。

函數類型

函數類型是派生類型。在這種情況下,該類型是由返回類型和其參數的數量和類型衍生出來的。函數的返回類型不能是數組類型。
當你聲明函數時,你使用函數聲明器來指定函數的名稱和返回類型。如果聲明器包括參數類型列表和定義,每個參數的聲明必須包括標識符,除了只有void類型參數的參數列表,它不需要標識符。
下麵是幾個函數類型的聲明:

int f(void);
int *fip();
void g(int i, int j);
void h(int, int);

首先,我們聲明沒有參數的函數f,返回int。接下來,我們聲明一個沒有指定參數的函數fip,它返回指向int的指針。最後,我們聲明兩個函數,g和h,每個函數都返回void,並接受兩個int類型的參數。
如果標識符是巨集,用標識符來指定參數(如這裡的g)可能會有問題。然而,提供參數名稱是自我記錄代碼的良好做法,所以通常不建議省略標識符(如對h的做法)。
在函數聲明中,指定參數是可選的。然而,不這樣做偶爾也會有問題。如果你用C++寫fip的函數聲明,它將聲明不接受任何參數的函數,並返回int *。在C語言中,fip聲明的是接受任何類型參數的函數,並返回一個int *。在C語言中,你不應該用空參數列表來聲明函數。首先,這是語言的廢棄功能,將來可能會被刪除。其次,這段代碼可能會被移植到C++中,所以要明確地列出參數類型,在沒有參數的時候使用void。
帶有參數類型列表的函數類型被稱為函數原型。函數原型告知編譯器一個函數所接受的參數的數量和類型。編譯器使用這些信息來驗證在函數定義和對函數的任何調用中是否使用了正確的參數數量和類型。

函數定義提供了該函數的實際實現。請看下麵的函數定義:

int max(int a, int b)
{ return a > b ? a : b; }

返回類型指定為int;函數聲明器為max(int a, int b);而函數主體為{ return a > b ? a : b; }。函數類型的指定不能包括任何類型限定符(參見第32頁 "類型限定符")。函數體本身使用了條件運算符(?

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、深拷貝 1.1、概念 對象的深拷貝是指其屬性與其拷貝的源對象的屬性不共用相同的引用(指向相同的底層值)的副本。 因此,當你更改源或副本時,可以確保不會導致其他對象也發生更改;也就是說,你不會無意中對源或副本造成意料之外的更改。 在深拷貝中,源和副本是完全獨立的。深拷貝與其源對象不共用引用,所以對 ...
  • 作者:京東科技 牛志偉 近期對Webpack5構建性能進行了優化,構建耗時從150s到60s再到10s,下麵詳細講解下優化過程。 優化前現狀 1.歷史項目基於Vue3 + Webpack5技術棧,其中webpack配置項由開發者自己維護(沒有使用@vue/cli-service),並且做了環境分離。 ...
  • 效果如下 代碼實現 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>帖子類別</title> <meta name="renderer" content="webkit"> <meta http-equiv="X-UA-Comp ...
  • 在JavaScript中,箭頭函數是一種簡化的函數語法,它在ES6(ECMAScript 2015)引入。箭頭函數的語法比傳統的function表達式更簡潔,同時還有一些特性,例如繼承外部作用域的this值。 箭頭函數的基本語法如下: (param1, param2, ..., paramN) => ...
  • 大家好,我是風箏 其實很早之前就想學學 VSCode 插件開發了,但是又不知道做什麼,加上我這半吊子前端水平,遲遲沒有動手。 最近 ChatGPT 火的一塌糊塗,我也一直在用,真的非常好用,有些問題之前需要 Google 搜索,現在用 ChatGPT 基本上都能直接解決,效率提升了不少。 但是吧,瀏 ...
  • 封裝函數 // 傳入 id、樹形結構數據 export function getParentTree(id, tree) { let arr = [] //要返回的數組 for (let i = 0; i < tree.length; i++) { let item = tree[i] arr = ...
  • 模式介紹 結構型模式(Structural Pattern)的主要目的就是將不同的類和對象組合在一起,形成更大或者更複雜的結構體。該模式並不是簡單地將這些類或對象擺放在一起,而是要提供它們之間的關聯方式。不同的結構型模式從不同的角度來組合類或對象,它們儘可能滿足各種面向對象設計原則的同時為類或對象的 ...
  • 學習DDD的意義 作為技術人,都有一個成為大牛的夢。 有些人可以通過自己掌握了比較底層、有深度、有難度的技術來證明自己的能力。 但對於絕大多數的應用研發工程師來說,其大部分的時間精力,會被消耗在讀不懂、講不清的屎山代碼中,以及複雜多變的業務迭代中。很少會有需要去接觸高深技術的機會,即便是接觸了,也很 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...