《程序設計實習》之【從C走進C++】

函數指針

基本概念

程序運行期間,每個函數都會佔用一 段連續的內存空間。而函數名就是該函數所 佔內存區域的起始地址(也稱“入口地址”)。 我們可以將函數的入口地址賦給一個指針變 量,使該指針變量指向該函數。然後通過指 針變量就可以調用這個函數。這種指向函數 的指針變量稱爲“函數指針”。

定義形式

類型名 (* 指針變量名)(參數類型1, 參數類型2,…);

例如:

int (*pf)(int, char);

表示pf是一個函數指針,它所指向的函數,返回值類型應是int, 該函數應有兩個參數,第一個是int 類型,第二個是char類型。

使用方法

可以用一個原型匹配的函數的名字給一個函數指針賦值。要通過函數指針調用它所指向的函數,寫法爲:

函數指針名(實參表);
#include <stdio.h>
void PrintMin(int a,int b) {
    if( a<b )
        printf("%d",a);
    else
        printf("%d",b);
}
int main() {
    void (* pf)(int ,int);
    int x = 4, y = 5;
    pf = PrintMin;
    pf(x,y);      // 輸出結果:4
    return 0;
}

函數指針和qsort函數

C語言快速排序庫函數:

void qsort(void *base, int nelem, unsigned int width,
int ( * pfCompare)( const void *, const void *));

可以對任意類型的數組進行排序。
對數組排序,需要知道:

  • 數組起始地址
  • 數組元素的個數
  • 每個元素的大小(由此可以算出每個元素的地址)
  • 元素誰在前誰在後的規則

pfCompare: 函數指針,它指向一個“比較函數”。
該比較函數應爲以下形式:

int 函數名(const void * elem1, const void * elem2);

比較函數是程序員自己編寫的。
排序就是一個不斷比較並交換位置的過程。
qsort函數在執行期間,會通過pfCompare指針調用 “比較函數”,調用時將要比較的兩個元素的地址傳給“比較函數”,然後根 據“比較函數”返回值判斷兩個元素哪個更應該排在前面。

比較函數編寫規則:

  • 如果 * elem1應該排在 * elem2前面,則函數返回值是負整數
  • 如果 * elem1和* elem2哪個排在前面都行,那麼函數返回0
  • 如果 * elem1應該排在 * elem2後面,則函數返回值是正整數

實例:
下面的程序,功能是調用qsort庫函數,將一個unsigned int數組按照個 位數從小到大進行排序。比如 8,23,15三個數,按個位數從小到大排 序,就應該是 23,15,8

#include <stdio.h>
#include <stdlib.h>
int MyCompare( const void * elem1, const void * elem2 ) {
    unsigned int * p1, * p2;
    p1 = (unsigned int *) elem1; // “* elem1” 非法
    p2 = (unsigned int *) elem2; // “* elem2” 非法
    return (* p1 % 10) - (* p2 % 10 );
}
#define NUM 5
int main() {
    unsigned int an[NUM] = { 8, 123, 11, 10, 4 };
    qsort(an, NUM, sizeof(unsigned int), MyCompare);
    for( int i = 0;i < NUM; i++ )
        printf("%d ",an[i]);
    return 0;
}

輸出結果:
10 11 123 4 8

命令行參數

命令行方式運行程序

notepad sample.txt

命令行參數

用戶在CMD窗口輸入可執行文件名的方式啓動程序時,跟在可執行文件名後面的那些字符串,稱爲“命令行參數”。命令行參數可以有多個,以空格分隔。比如,在CMD窗口敲:

copy file1.txt file2.txt

“copy”, “file1.txt”, “file2.txt”就是命令行參數。如何在程序中獲得命令行參數呢?

int main(int argc, char * argv[]) {
    ……
}

argc: 代表啓動程序時,命令行參數的個數。C/C++語言規定,可執行程序程序本身的文件名,也算一個命令行參數,因此,argc的值 至少是1。
argv: 指針數組,其中的每個元素都是一個char* 類型的指針,該指針指向一個字符串,這個字符串裏就存放着命令行參數。
例如,argv[0]指向的字符串就是第一個命令行參數,即可執行程序的文件名,argv[1]指向第二個命令行參數,argv[2]指向第三個命令行參數……。

#include <stdio.h>
int main(int argc, char * argv[]) {
    for(int i = 0;i < argc; i ++ )
        printf( "%s\n",argv[i]);
    return 0;
}

將上面的程序編譯成sample.exe,然後在控制檯窗口敲:

sample para1 para2 s.txt 5 “hello world”

位運算

基本概念

位運算:
用於對整數類型(int,char, long 等)變量中的某一位(bit),或者若干位進行操作。比如:
1) 判斷某一位是否爲1
2) 只改變其中某一位,而保持其他位都不變

C/C++語言提供了六種位運算符來進行位運算操作:
& 按位與(雙目)
| 按位或(雙目)
^ 按位異或(雙目)
~ 按位非(取反)(單目)
<< 左移(雙目)
>> 右移(雙目)

& 按位與

將參與運算的兩操作數各對應的二進制位進行與操作,只有對應的兩個二進位均爲1時,結果的對應二進制位才爲1,否則爲0。

例如:表達式“21 & 18 ”的計算結果是16(即二進制數10000),因爲:

21 用二進制表示就是:
0000 0000 0000 0000 0000 0000 0001 0101
18 用二進制表示就是:
0000 0000 0000 0000 0000 0000 0001 0010
二者按位與所得結果是:
0000 0000 0000 0000 0000 0000 0001 0000

通常用來將某變量中的某些位清0且同時保留其他位不變。 也可以用來獲取某變量中的某一位。

例如,如果需要將int型變量n的低8位全置成0,而其餘位不變,則可以執行:

n = n & 0xffffff00;

也可以寫成:

n &= 0xffffff00;

如果n是short類型的,則只需執行:

n &= 0xff00;

如何判斷一個int型變量n的第7位(從右往左,從0開始數) 是否是1 ?
只需看表達式 “n & 0x80”的值是否等於0x80即可。

0x80: 1000 0000

| 按位或

將參與運算的兩操作數各對應的二進制位進行或操 作,只有對應的兩個二進位都爲0時,結果的對應 二進制位纔是0,否則爲1。

例如:表達式“21 | 18 ”的值是23,因爲:

21:     0000 0000 0000 0000 0000 0000 0001 0101
18:     0000 0000 0000 0000 0000 0000 0001 0010
21|18:  0000 0000 0000 0000 0000 0000 0001 0111

按位或運算通常用來將某變量中的某些位置1且保留其他位不變。
例如,如果需要將int型變量n的低8位全置成1,而其餘位不變,則可以執行:

n |= 0xff;
0xff: 1111 1111

^ 按位異或

將參與運算的兩操作數各對應的二進制位進行異或操作,即只有對應的兩個二進位不相同時,結果的對應二進制位纔是1,否則爲0。

例如:表達式“21 ^ 18 ”的值是7(即二進制數111)。

21:    0000 0000 0000 0000 0000 0000 0001 0101
18:    0000 0000 0000 0000 0000 0000 0001 0010
21^18: 0000 0000 0000 0000 0000 0000 0000 0111

按位異或運算通常用來將某變量中的某些位取反, 且保留其他位不變。
例如,如果需要將int型變量n的低8位取反,而其餘位不變,則可以執行:

n ^= 0xff;
0xff: 1111 1111

異或運算的特點是:
如果 a^b=c,那麼就有 c^b = a以及c^a=b。(窮舉法可證)
此規律可以用來進行最簡單的加密和解密。

另外異或運算還能實現不通過臨時變量,就能交換兩個變量的值:

int a = 5, b = 7;
a = a ^ b;
b = b ^ a;
a = a ^ b;

即實現a,b值交換,窮舉法可證。

~ 按位非

按位非運算符“~”是單目運算符。其功能是將操作數中的二進制位0變成1,1變成0。

例如,表達式“~21”的值是整型數 0xffffffea

21: 0000 0000 0000 0000 0000 0000 0001 0101

~21:1111 1111 1111 1111 1111 1111 1110 1010

<< 左移運算符

表達式:a << b
的值是:將a各二進位全部左移b位後得到的值。左移時,高位 丟棄,低位補0。a 的值不因運算而改變。

例如: 9 << 4
9的二進制形式:
0000 0000 0000 0000 0000 0000 0000 1001
因此,表達式“9<<4”的值,就是將上面的二進制數左移4位,得:
0000 0000 0000 0000 0000 0000 1001 0000
即爲十進制的144。

實際上,左移1位,就等於是乘以2,左移n位,就等於 是乘以2^n 。而左移操作比乘法操作快得多。

>> 右移運算符

表達式:a >> b的值是:將a各二進位全部右移b位後得到的值。右移時,移出最右邊 的位就被丟棄。 a 的值不因運算而改變。
對於有符號數,如long,int,short,char類型變量,在右移時,符號位(即最高位)將一起移動,並且大多數C/C++編譯器規定,如果原符號位爲1,則右移時高位就補充1,原符號位爲0,則右移時高位就補充 0。

實際上,右移n位,就相當於左操作數除以2^n ,並且將結果往小裏取整。

-25 >> 4 = -2
-2 >> 4 = -1
18 >> 4 = 1
#include <stdio.h>
int main() {
    int n1 = 15;
    short n2 = -15;
    unsigned short n3 = 0xffe0;
    char c = 15;
    n1 = n1>>2;
    n2 >>= 3;
    n3 >>= 4;
    c >>= 3;
    printf( "n1=%d,n2=%x,n3=%x,c=%x",n1,n2,n3,c);
}    //輸出結果是:n1=3,n2=fffffffe,n3=ffe,c=1
n1: 0000 0000 0000 0000 0000 0000 0000 1111
n1 >>= 2:
變成3
    0000 0000 0000 0000 0000 0000 0000 0011

n2: 1111 1111 1111 0001
n2 >>= 3:
變成 fffffffe, 即-2
    1111 1111 1111 1110

n3: 1111 1111 1110 0000
n3 >>= 4:
變成 ffe
    0000 1111 1111 1110

c: 0000 1111
c >>= 3;
變成1
   0000 0001

思考題:

有兩個int型的變量a和n(0 <= n <= 31),要求寫一個表達式,使該表達式的值和a的第n位相同。
答案:

( a >> n ) & 1

當0<=n<31時,還可以寫爲:

(a & (1 << n )) >> n

當n=31時,1<<31爲符號位,與a做與運算後再右移,可能由於符號位問題導致最後結果第n位之前不都爲0了。

引用

引用的概念

下面的寫法定義了一個引用,並將其初始化爲引用某個變量。
類型名 & 引用名 = 某變量名;

int n = 4;
int & r = n;     // r引用了 n, r的類型是 int &

某個變量的引用,等價於這個變量,相當於該變量的一個別名。

int n = 4;
int & r = n;
r = 4;
cout << r;   //輸出 4
cout << n;   //輸出 4
n = 5;
cout << r;   //輸出 5

注意:

  • 定義引用時一定要將其初始化成引用某個變量。
  • 初始化後,它就一直引用該變量,不會再引用別的變量了。
  • 引用只能引用變量,不能引用常量和表達式。
double a = 4, b = 5;
double & r1 = a;
double & r2 = r1;
r2 = 10;   // r2也引用 a
cout << a << endl; // 輸出 10
r1 = b;  // r1並沒有引用b,只是對r1引用的變量賦值
cout << a << endl; //輸出 5

C語言中,如何編寫交換兩個整型變量值的函數?

void swap( int * a, int * b) {
    int tmp;
    tmp = * a; * a = * b; * b = tmp;
}
int n1, n2;
swap(& n1,& n2) ; // n1,n2的值被交換

有了C++的引用:

void swap( int & a, int & b) {
    int tmp;
    tmp = a; a = b; b = tmp;
}
int n1, n2;
swap(n1,n2);  // n1,n2的值被交換

const

const關鍵字的用法

const int MAX_VAL = 23;
const string SCHOOL_NAME = “Peking University”;
  • 不可通過常量指針修改其指向的內容
int n, m;
const int * p = & n;
* p = 5; //編譯出錯
n = 4;   //ok
p = &m;  //ok, 常量指針的指向可以變化
  • 不能把常量指針賦值給非常量指針,反過來可以
const int * p1; 
int * p2;
p1 = p2;   //ok
p2 = p1;   //error
p2 = (int * ) p1;  //ok, 強制類型轉換
  • 函數參數爲常量指針時,可避免函數內部不小心改變參數指針所指地方的內容
void MyPrintf( const char * p ) {
    strcpy( p,"this"); //編譯出錯,strcpy()函數第一個參數爲char*類型,但p是const char *類型的,不可以把常量指針賦值給非常量指針
    printf("%s",p);    //ok
}
  • 不能通過常引用修改其引用的變量
int n;
const int & r = n;
r = 5; //error
n = 4; //ok

動態內存分配

new運算符實現動態內存分配

  • 第一種用法,分配一個變量:
P = new T;

T是任意類型名,P是類型爲T *的指針。
動態分配出一片大小爲sizeof(T)字節的內存空間,並且將該 內存空間的起始地址賦值給P。比如:

int * pn;
pn = new int;
* pn = 5;
  • 第二種用法,分配一個數組:
P = new T[N];

T:任意類型名
P :類型爲T * 的指針
N :要分配的數組元素的個數,可以是整型表達式
動態分配出一片大小爲 N*sizeof(T)字節的內存空間,並且將該內存空間的起始地址賦值給P。

int * pn;
int i = 5;
pn = new int[i * 20];
pn[0] = 20;
pn[100] = 30;  //編譯沒問題。運行時導致數組越界
  • new動態分配的內存空間,一定要用 delete運算符進行釋放
delete 指針; //該指針必須指向new出來的空間
int * p = new int;
* p = 5;
delete p;
delete p;  //導致異常,一片空間不能被delete多次
  • delete釋放動態分配的數組,要加[]
delete [ ] 指針;//該指針必須指向new出來的數組
int * p = new int[20];
p[0] = 1;
delete [ ] p;

內聯函數和重載函數

內聯函數

函數調用是有時間開銷的。如果函數本身只有幾條語句,執行非常快,而且函數被反覆執行很多次,相比之下調用函數所產生的這個開銷就會顯得比較大。

爲了減少函數調用的開銷,引入了內聯函數機制。編譯器處理對內聯函數的調用語句時,是將整個函數的代碼插入到調用語句處,而不會產生調用函數的語句。

inline int Max(int a,int b) {
    if( a > b) return a;
    return b;
}

k = Max(n1, n2);
// 在程序中上面的語句會被展開成如下形式:
if (n1 > n2)
    tmp = n1;
else
    tmp = n2;
k = tmp;

函數重載

一個或多個函數,名字相同,然而參數個數參數類型不相同,這叫做函數的重載。

  • 以下三個函數是重載關係:
int Max(double f1,double f2) {}
int Max(int n1,int n2) { }
int Max(int n1,int n2,int n3) { }
  • 函數重載使得函數命名變得簡單。
  • 編譯器根據調用語句的中的實參的個數和類型判斷應該調用哪個函數。
Max(3.4,2.5);  //調用 (1)
Max(2,4);      //調用 (2)
Max(1,2,3);    //調用 (3)
Max(3,2.4);    //error,二義性

函數缺省函數

C++中,定義函數的時候可以讓最右邊的連續若干個參數有缺省值,那麼調用函數的時候,若相應位置不寫參數,參數就是缺省值。

void func( int x1, int x2 = 2, int x3 = 3) { }
func(10 ) ; //等效於 func(10,2,3)
func(10,8) ; //等效於 func(10,8,3)
func(10, , 8) ; //不行,只能最右邊的連續若干個參數缺省

函數參數可缺省的目的在於提高程序的可擴充性。

即如果某個寫好的函數要添加新的參數,而原先那些 調用該函數的語句,未必需要使用新增的參數,那麼 爲了避免對原先那些函數調用語句的修改,就可以使 用缺省參數。

發佈了97 篇原創文章 · 獲贊 27 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章