1. 程式人生 > 其它 >python assert斷言函式

python assert斷言函式

定義指標變數

定義指標變數與定義普通變數非常類似,不過要在變數名前面加星號*,格式為:

datatype *name;

或者

datatype *name = value;

*表示這是一個指標變數,datatype表示該指標變數所指向的資料的型別 。例如:

int *p1;

p1 是一個指向 int 型別資料的指標變數,至於 p1 究竟指向哪一份資料,應該由賦予它的值決定。再如:

int a = 100;
int *p_a = &a;

在定義指標變數 p_a 的同時對它進行初始化,並將變數 a 的地址賦予它,此時 p_a 就指向了 a。值得注意的是,p_a 需要的一個地址,a 前面必須要加取地址符&

,否則是不對的。

和普通變數一樣,指標變數也可以被多次寫入,只要你想,隨時都能夠改變指標變數的值,請看下面的程式碼:

//定義普通變數
float a = 99.5, b = 10.6;
char c = '@', d = '#';
//定義指標變數
float *p1 = &a;char *p2 = &c;
//修改指標變數的值
p1 = &b;
p2 = &d;

*是一個特殊符號,表明一個變數是指標變數,定義 p1、p2 時必須帶*。而給 p1、p2 賦值時,因為已經知道了它是一個指標變數,就沒必要多此一舉再帶上*,後邊可以像使用普通變數一樣來使用指標變數。也就是說,定義指標變數時必須帶*

,給指標變數賦值時不能帶*

需要強調的是,p1、p2 的型別分別是float*char*,而不是floatchar,它們是完全不同的資料型別,讀者要引起注意。

指標變數儲存了資料的地址,通過指標變數能夠獲得該地址上的資料,格式為:

*pointer;

這裡的*稱為指標運算子,用來取得某個地址上的資料,請看下面的例子:

純文字複製
#include <stdio.h>int main(){
int a = 15;
int *p = &a;
printf("%d, %d\n", a, *p); //兩種方式都可以輸出a的值
return 0;
}

CPU 讀寫資料必須要知道資料在記憶體中的地址,普通變數和指標變數都是地址的助記符,雖然通過 *p 和 a 獲取到的資料一樣,但它們的執行過程稍有不同:a 只需要一次運算就能夠取得資料,而 *p 要經過兩次運算,多了一層“間接”。

假設變數 a、p 的地址分別為 0X1000、0XF0A0,它們的指向關係如下圖所示:

程式被編譯和連結後,a、p 被替換成相應的地址。使用 *p 的話,要先通過地址 0XF0A0 取得變數 p 本身的值,這個值是變數 a 的地址,然後再通過這個值取得變數 a 的資料,前後共有兩次運算;而使用 a 的話,可以通過地址 0X1000 直接取得它的資料,只需要一步運算。

也就是說,使用指標是間接獲取資料,使用變數名是直接獲取資料,前者比後者的代價要高。

*在不同的場景下有不同的作用:*可以用在指標變數的定義中,表明這是一個指標變數,以和普通變數區分開;使用指標變數時在前面加*表示獲取指標指向的資料,或者說表示的是指標指向的資料本身。

指標除了可以獲取記憶體上的資料,也可以修改記憶體上的資料,例如:

#include <stdio.h>int main(){
int a = 15, b = 99, c = 222;
int *p = &a; //定義指標變數
*p = b; //通過指標變數修改記憶體上的資料
c = *p; //通過指標變數獲取記憶體上的資料
printf("%d, %d, %d, %d\n", a, b, c, *p);
return 0;
}

執行結果: 99, 99, 99, 99

*p 代表的是 a 中的資料,它等價於 a,可以將另外的一份資料賦值給它,也可以將它賦值給另外的一個變數。

也就是說,定義指標變數時的*和使用指標變數時的*意義完全不同。以下面的語句為例:

int *p = &a;
*p = 100;

第1行程式碼中*用來指明 p 是一個指標變數,第2行程式碼中*用來獲取指標指向的資料。

需要注意的是,給指標變數本身賦值時不能加*。修改上面的語句:

int *p;
p = &a;
*p = 100;

第2行程式碼中的 p 前面就不能加*

指標變數也可以出現在普通變數能出現的任何表示式中,例如:

純文字複製
int x, y, *px = &x, *py = &y;
y = *px + 5; //表示把x的內容加5並賦給y,*px+5相當於(*px)+5
y = ++*px; //px的內容加上1之後賦給y,++*px相當於++(*px)
y = *px++; //相當於y=*(px++)
py = px; //把一個指標的值賦給另一個指標

關於 * 和 & 的謎題

假設有一個 int 型別的變數 a,pa 是指向它的指標,那麼*&a&*pa分別是什麼意思呢?

*&a可以理解為*(&a)&a表示取變數 a 的地址(等價於 pa),*(&a)表示取這個地址上的資料(等價於 *pa),繞來繞去,又回到了原點,*&a仍然等價於 a。

&*pa可以理解為&(*pa)*pa表示取得 pa 指向的資料(等價於 a),&(*pa)表示資料的地址(等價於 &a),所以&*pa等價於 pa。

對星號*的總結

在我們目前所學到的語法中,星號*主要有三種用途:

  • 表示乘法,例如int a = 3, b = 5, c; c = a * b;,這是最容易理解的。

  • 表示定義一個指標變數,以和普通變數區分開,例如int a = 100; int *p = &a;

  • 表示獲取指標指向的資料,是一種間接操作,例如int a, b, *p = &a; *p = 100; b = *p;

不能對指標變數進行乘法、除法、取餘等其他運算,除了會發生語法錯誤,也沒有實際的含義。

陣列(Array)是一系列具有相同型別的資料的集合,每一份資料叫做一個數組元素(Element)。陣列中的所有元素在記憶體中是連續排列的,整個陣列佔用的是一塊記憶體。以int arr[] = { 99, 15, 100, 888, 252 };為例,該陣列在記憶體中的分佈如下圖所示:

定義陣列時,要給出陣列名和陣列長度,陣列名可以認為是一個指標,它指向陣列的第 0 個元素。在C語言中,我們將第 0 個元素的地址稱為陣列的首地址。以上面的陣列為例,下圖是 arr 的指向:

陣列名的本意是表示整個陣列,也就是表示多份資料的集合,但在使用過程中經常會轉換為指向陣列第 0 個元素的指標,所以上面使用了“認為”一詞,表示陣列名和陣列首地址並不總是等價。初學者可以暫時忽略這個細節,把陣列名當做指向第 0 個元素的指標使用即可

下面的例子演示瞭如何以指標的方式遍歷陣列元素:

#include <stdio.h>
int main(){
int arr[] = { 99, 15, 100, 888, 252 };
int len = sizeof(arr) / sizeof(int); //求陣列長度
int i;
for(i=0; i<len; i++){
printf("%d ", *(arr+i) ); //*(arr+i)等價於arr[i]
}
printf("\n");
return 0;
}

執行結果: 99 15 100 888 252

第 8 行程式碼中我們使用了*(arr+i)這個表示式,arr 是陣列名,指向陣列的第 0 個元素,表示陣列首地址, arr+i 指向陣列的第 i 個元素,*(arr+i) 表示取第 i 個元素的資料,它等價於 arr[i]。

我們也可以定義一個指向陣列的指標,例如:

int arr[] = { 99, 15, 100, 888, 252 };
int *p = arr;

arr 本身就是一個指標,可以直接賦值給指標變數 p。arr 是陣列第 0 個元素的地址,所以int *p = arr;也可以寫作int *p = &arr[0];。也就是說,arr、p、&arr[0] 這三種寫法都是等價的,它們都指向陣列第 0 個元素,或者說指向陣列的開頭。

如果一個指標指向了陣列,我們就稱它為陣列指標(Array Pointer)。

陣列指標指向的是陣列中的一個具體元素,而不是整個陣列,所以陣列指標的型別和陣列元素的型別有關,上面的例子中,p 指向的陣列元素是 int 型別,所以 p 的型別必須也是int *

反過來想,p 並不知道它指向的是一個數組,p 只知道它指向的是一個整數,究竟如何使用 p 取決於程式設計師的編碼。

更改上面的程式碼,使用陣列指標來遍歷陣列元素:

#include <stdio.h>
int main(){
int arr[] = { 99, 15, 100, 888, 252 };
int i, *p = arr, len = sizeof(arr) / sizeof(int);
for(i=0; i<len; i++){
printf("%d ", *(p+i) );
}
printf("\n");
return 0;
}

陣列在記憶體中只是陣列元素的簡單排列,沒有開始和結束標誌,在求陣列的長度時不能使用sizeof(p) / sizeof(int),因為 p 只是一個指向 int 型別的指標,編譯器並不知道它指向的到底是一個整數還是一系列整數(陣列),所以 sizeof(p) 求得的是 p 這個指標變數本身所佔用的位元組數,而不是整個陣列佔用的位元組數。

C語言字串指標(指向字串的指標)

C語言中沒有特定的字串型別,我們通常是將字串放在一個字元陣列中

#include <stdio.h>
#include <string.h>
int main(){
char str[] = "http://c.biancheng.net";
int len = strlen(str), i; //直接輸出字串
printf("%s\n", str); //每次輸出一個字元
for(i=0; i<len; i++){
printf("%c", str[i]);
}
printf("\n");
return 0;
}

執行結果: http://c.biancheng.net http://c.biancheng.net

除了字元陣列,C語言還支援另外一種表示字串的方法,就是直接使用一個指標指向字串,例如:

char *str = "http://c.biancheng.net";

或者:

char *str;
str = "http://c.biancheng.net";

字串中的所有字元在記憶體中是連續排列的,str 指向的是字串的第 0 個字元;我們通常將第 0 個字元的地址稱為字串的首地址。字串中每個字元的型別都是char,所以 str 的型別也必須是char *

這一切看起來和字元陣列是多麼地相似,它們都可以使用%s輸出整個字串,都可以使用*[ ]獲取單個字元,這兩種表示字串的方式是不是就沒有區別了呢?

有!它們最根本的區別是在記憶體中的儲存區域不一樣,字元陣列儲存在全域性資料區或棧區,第二種形式的字串儲存在常量區。全域性資料區和棧區的字串(也包括其他資料)有讀取和寫入的許可權,而常量區的字串(也包括其他資料)只有讀取許可權,沒有寫入許可權。

記憶體許可權的不同導致的一個明顯結果就是,字元陣列在定義後可以讀取和修改每個字元,而對於第二種形式的字串,一旦被定義後就只能讀取不能修改,任何對它的賦值都是錯誤的。

我們將第二種形式的字串稱為字串常量,意思很明顯,常量只能讀取不能寫入。請看下面的演示:

#include <stdio.h>
int main(){
char *str = "Hello World!";
str = "I love C!"; //正確
str[3] = 'P'; //錯誤
return 0;
}

這段程式碼能夠正常編譯和連結,但在執行時會出現段錯誤(Segment Fault)或者寫入位置錯誤。

第4行程式碼是正確的,可以更改指標變數本身的指向;第5行程式碼是錯誤的,不能修改字串中的字元。

C語言指標變數作為函式引數

在C語言中,函式的引數不僅可以是整數、小數、字元等具體的資料,還可以是指向它們的指標。用指標變數作函式引數可以將函式外部的地址傳遞到函式內部,使得在函式內部可以操作函式外部的資料,並且這些資料不會隨著函式的結束而被銷燬。

像陣列、字串、動態分配的記憶體等都是一系列資料的集合,沒有辦法通過一個引數全部傳入函式內部,只能傳遞它們的指標,在函式內部通過指標來影響這些資料集合。

有的時候,對於整數、小數、字元等基本型別資料的操作也必須要藉助指標,一個典型的例子就是交換兩個變數的值

用陣列作函式引數

陣列是一系列資料的集合,無法通過引數將它們一次性傳遞到函式內部,如果希望在函式內部運算元組,必須傳遞陣列指標。下面的例子定義了一個函式 max(),用來查詢陣列中值最大的元素:

#include <stdio.h>
int max(int *intArr, int len){
int i, maxValue = intArr[0]; //假設第0個元素是最大值
for(i=1; i<len; i++){
if(maxValue < intArr[i]){
maxValue = intArr[i];
}
}
return maxValue;
}
int main(){
int nums[6], i;
int len = sizeof(nums)/sizeof(int); //讀取使用者輸入的資料並賦值給陣列元素
for(i=0; i<len; i++){
scanf("%d", nums+i);
}
printf("Max value is %d!\n", max(nums, len));
return 0;
}

執行結果: 12 55 30 8 93 27↙ Max value is 93!

C語言為什麼不允許直接傳遞陣列的所有元素,而必須傳遞陣列指標呢?

引數的傳遞本質上是一次賦值的過程,賦值就是對記憶體進行拷貝。所謂記憶體拷貝,是指將一塊記憶體上的資料複製到另一塊記憶體上。

對於像 int、float、char 等基本型別的資料,它們佔用的記憶體往往只有幾個位元組,對它們進行記憶體拷貝非常快速。而陣列是一系列資料的集合,資料的數量沒有限制,可能很少,也可能成千上萬,對它們進行記憶體拷貝有可能是一個漫長的過程,會嚴重拖慢程式的效率,為了防止技藝不佳的程式設計師寫出低效的程式碼,C語言沒有從語法上支援資料集合的直接賦值。

C語言指標作為函式返回值

言允許函式的返回值是一個指標(地址),我們將這樣的函式稱為指標函式。

純文字複製
#include <stdio.h>
#include <string.h>
char *strlong(char *str1, char *str2){
if(strlen(str1) >= strlen(str2)){
return str1;
}
else{
return str2;
}
}
int main(){
char str1[30], str2[30], *str;
gets(str1);
gets(str2);
str = strlong(str1, str2);
printf("Longer string: %s\n", str);
return 0;
}

用指標作為函式返回值時需要注意的一點是,函式執行結束後會銷燬在它內部定義的所有區域性資料,包括區域性變數、區域性陣列和形式引數,函式返回的指標請儘量不要指向這些資料,C語言沒有任何機制來保證這些資料會一直有效,它們在後續使用過程中可能會引發執行時錯誤。請看下面的例子:

#include <stdio.h>int *func(){    int n = 100;    return &n;}int main(){    int *p = func(), n;    n = *p;    printf("value = %d\n", n);    return 0;}

執行結果:

value = 100

n 是 func() 內部的區域性變數,func() 返回了指向 n 的指標,根據上面的觀點,func() 執行結束後 n 將被銷燬,使用 *p 應該獲取不到 n 的值。但是從執行結果來看,我們的推理好像是錯誤的,func() 執行結束後 *p 依然可以獲取區域性變數 n 的值,這個上面的觀點不是相悖嗎?

為了進一步看清問題的本質,不妨將上面的程式碼稍作修改,在第9~10行之間增加一個函式呼叫,看看會有什麼效果:

#include <stdio.h>
int *func(){
int n = 100;
return &n;
}
int main(){
int *p = func(), n;
printf("c.biancheng.net\n");
n = *p;
printf("value = %d\n", n);
return 0;
}

執行結果:

c.biancheng.net value = -2

可以看到,現在 p 指向的資料已經不是原來 n 的值了,它變成了一個毫無意義的甚至有些怪異的值。與前面的程式碼相比,該段程式碼僅僅是在 *p 之前增加了一個函式呼叫

C語言二級指標(指向指標的指標)詳解

指標可以指向一份普通型別的資料,例如 int、double、char 等,也可以指向一份指標型別的資料,例如 int *、double *、char * 等。

如果一個指標指向的是另外一個指標,我們就稱它為二級指標,或者指向指標的指標。

假設有一個 int 型別的變數 a,p1是指向 a 的指標變數,p2 又是指向 p1 的指標變數,它們的關係如下圖所示:

將這種關係轉換為C語言程式碼:

int a =100;int *p1 = &a;int **p2 = &p1;

指標變數也是一種變數,也會佔用儲存空間,也可以使用&獲取它的地址。C語言不限制指標的級數,每增加一級指標,在定義指標變數時就得增加一個星號*。p1 是一級指標,指向普通型別的資料,定義時有一個*;p2 是二級指標,指向一級指標 p1,定義時有兩個*

C語言指標陣列(陣列每個元素都是指標)詳解

指標陣列還可以和字串陣列結合使用,請看下面的例子:

#include <stdio.h>
int main(){
char *str[3] = {
"c.biancheng.net",
"C語言中文網",
"C Language" };
printf("%s\n%s\n%s\n", str[0], str[1], str[2]);
return 0;
}

執行結果: c.biancheng.net C語言中文網 C Language

需要注意的是,字元陣列 str 中存放的是字串的首地址,不是字串本身,字串本身位於其他的記憶體區域,和字元陣列是分開的。

也只有當指標陣列中每個元素的型別都是char *時,才能像上面那樣給指標陣列賦值,其他型別不行。

C語言二維陣列指標(指向二維陣列的指標)詳解

二維陣列在概念上是二維的,有行和列,但在記憶體中所有的陣列元素都是連續排列的,它們之間沒有“縫隙”。以下面的二維陣列 a 為例:

int a3 = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };

從概念上理解,a 的分佈像一個矩陣:

0   1   2   3
4 5 6 7
8 9 10 11

但在記憶體中,a 的分佈是一維線性的,整個陣列佔用一塊連續的記憶體:

C語言中的二維陣列是按行排列的,也就是先存放 a[0] 行,再存放 a[1] 行,最後存放 a[2] 行;每行中的 4 個元素也是依次存放。陣列 a 為 int 型別,每個元素佔用 4 個位元組,整個陣列共佔用 4×(3×4) = 48 個位元組。

C語言允許把一個二維陣列分解成多個一維陣列來處理。對於陣列 a,它可以分解成三個一維陣列,即 a[0]、a[1]、a[2]。每一個一維陣列又包含了 4 個元素,例如 a[0] 包含 a0、a0、a0、a0

假設陣列 a 中第 0 個元素的地址為 1000,那麼每個一維陣列的首地址如下圖所示:

為了更好的理解指標和二維陣列的關係,我們先來定義一個指向 a 的指標變數 p:

int (*p)[4] = a;

括號中的*表明 p 是一個指標,它指向一個數組,陣列的型別為int [4],這正是 a 所包含的每個一維陣列的型別。

[ ]的優先順序高於*( )是必須要加的,如果赤裸裸地寫作int *p[4],那麼應該理解為int *(p[4]),p 就成了一個指標陣列,而不是二維陣列指標,這在《C語言指標陣列》中已經講到。

對指標進行加法(減法)運算時,它前進(後退)的步長與它指向的資料型別有關,p 指向的資料型別是int [4],那麼p+1就前進 4×4 = 16 個位元組,p-1就後退 16 個位元組,這正好是陣列 a 所包含的每個一維陣列的長度。也就是說,p+1會使得指標指向二維陣列的下一行,p-1會使得指標指向陣列的上一行。

陣列名 a 在表示式中也會被轉換為和 p 等價的指標!

下面我們就來探索一下如何使用指標 p 來訪問二維陣列中的每個元素。按照上面的定義: \1) p指向陣列 a 的開頭,也即第 0 行;p+1前進一行,指向第 1 行。

\2) *(p+1)表示取地址上的資料,也就是整個第 1 行資料。注意是一行資料,是多個數據,不是第 1 行中的第 0 個元素,下面的執行結果有力地證明了這一點:

#include <stdio.h>int main(){    int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };    int (*p)[4] = a;    printf("%d\n", sizeof(*(p+1)));    return 0;}

執行結果: 16

\3) *(p+1)+1表示第 1 行第 1 個元素的地址。如何理解呢?

*(p+1)單獨使用時表示的是第 1 行資料,放在表示式中會被轉換為第 1 行資料的首地址,也就是第 1 行第 0 個元素的地址,因為使用整行資料沒有實際的含義,編譯器遇到這種情況都會轉換為指向該行第 0 個元素的指標;就像一維陣列的名字,在定義時或者和 sizeof、& 一起使用時才表示整個陣列,出現在表示式中就會被轉換為指向陣列第 0 個元素的指標。

\4) *(*(p+1)+1)表示第 1 行第 1 個元素的值。很明顯,增加一個 * 表示取地址上的資料。

根據上面的結論,可以很容易推出以下的等價關係:

a+i == p+i a[i] == p[i] == *(a+i) == *(p+i) ai == pi == *(a[i]+j) == *(p[i]+j) == ((a+i)+j) == ((p+i)+j)

【例項】使用指標遍歷二維陣列。

#include <stdio.h>
int main(){
int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
int(*p)[4];
int i,j;
p=a;
for(i=0; i<3; i++){
for(j=0; j<4; j++)
printf("%2d ",*(*(p+i)+j));
printf("\n");
}
return 0;
}

執行結果:

 0   1   2   3
4 5 6 7
8 9 10 11

指標陣列和二維陣列指標的區別

指標陣列和二維陣列指標在定義時非常相似,只是括號的位置不同:

int *(p1[5]);  //指標陣列,可以去掉括號直接寫作 int *p1[5];int (*p2)[5];  //二維陣列指標,不能去掉括號

指標陣列和二維陣列指標有著本質上的區別:指標陣列是一個數組,只是每個元素儲存的都是指標,以上面的 p1 為例,在32位環境下它佔用 4×5 = 20 個位元組的記憶體。二維陣列指標是一個指標,它指向一個二維陣列,以上面的 p2 為例,它佔用 4 個位元組的記憶體