【C++程式設計】1.從c走進c++
1.函式指標
(1)基本概念:程式執行期間,每個函式都會佔用一段連續的記憶體空間。而函式名就是該函式所佔記憶體區域的起始位置(也稱入口地址)。我們可以將一個函式的入口地址賦值給一個指標變數,使該指標變數指向該函式。然後通過指標變數就可以呼叫這個函式。這種指向函式的指標被稱為函式指標。
(2)函式指標定義形式:
型別名(*指標變數名)(引數型別1,引數型別2,....)
例如:int(*pf)(int,char); 表示pf是一個函式指標,它所指向的函式返回值應該是int,該函式應有兩個引數,第一個是Int型別,第二個是char型別。
(3)使用方法:可以用一個原型匹配的函式的名字給一個函式指標賦值。要通過函式指標呼叫它所指向的函式,寫法為:
函式指標名(實參表);
例:
#include<stdio.h> #include<iostream> #include<stdlib.h> using namespace std; void PrinMin(int a, int b) { if (a < b) cout << a << endl; else cout << b << endl; } int main() { void(*pf)(int, int); int x = 4, y = 5; pf = PrinMin; pf(x, y); getchar(); return 0; }
(4)函式指標和qsort庫
void qsort(void*base,int nelem,snsigned int width,int(*pfCompare)(const void*,const void*));
可以對任意型別的陣列進行排序
對陣列排序,需要知道:
(1)陣列起始地址
(2)陣列元素個數
(3)每個元素的大小(由此可以算出每個元素的地址)
(4)元素誰在前誰在後的規則
base:待排序陣列的起始地址,nelem:待排序陣列元素個數,width:待排序陣列的每個元素的大小(以位元組為單位)
pfCompare:比較函式的地址。比較函式是自己編寫的。
排序就是一個不斷比較並交換位置的過程。qsort函式在執行期間,會通過pfCompare指標呼叫“比較函式”,呼叫時將要比較的兩個元素的地址傳給“比較函式”,然後根據“比較函式”返回值判斷兩個元素哪個更應該排在前面。
比較函式編寫規則:int 比較函式名(const void*elem1,const void* elem2)
a.如果*elem1應該排在*elem2前面,則函式返回值是負整數。
b.如果*elem1和*elem2哪個排在前面都行,那麼函式返回0
c.如果*elem1應該排在*elem2後面,則函式返回值是正整數
例程:將數字按個位數從小到大排序
#include<stdio.h>
#include<iostream>
#include<stdlib.h>
using namespace std;
int MyCompare(const void*elem1, const void*elem2)
{
unsigned int*p1;
unsigned int*p2;
p1 = (unsigned int*)elem1;
p2 = (unsigned int*)elem2;
return *p1 % 10 - *p2 % 10;
}
#define NUM 5
int main()
{
unsigned int a[NUM] = { 5,9,13,14,28 };
qsort(a, NUM, sizeof(unsigned int), MyCompare);
for (int i = 0; i < NUM; i++)
cout << a[i]<<endl;
system("pause");
getchar();
return 0;
}
2.命令列引數
int main(int argc,char* argv[])
{
........
}
argc:代表啟動程式時,命令列引數的個數。C/C++語言規定,可執行程式程式本身的檔名,也算一個命令列引數,因此argc的值至少是1.
argv:指標陣列,其中每一個元素都是一個char*型別的指標,該指標指向一個字串,這個字串裡就存放著命令列引數。
例如,argv[0]指向的字串就是第一個命令列引數,即可執行程式的檔名,argv[1]指向第二個命令列引數,argv[2]指向第三個命令列引數。
#include<stdio.h>
#include<iostream>
#include<stdlib.h>
using namespace std;
int main(int argc, char*argv[])
{
for (int i = 0; i < argc; i++)
cout << argv[i] << endl;
return 0;
}
將上面的程式編譯成sample.exe,然後在控制檯視窗敲:
sample para1 pare2 s.txt5 "hello world"
3.位運算
有時候需要對某個整數型別變數中的某一位進行操作,例如,判斷某一位是否為1或只改變其中某一位而保持其他位都不變。C++語言提供了6種位運算子來進行“位運算”操作:按位與(&)、按位或(|)、按位異或(^)、取反(~)、左移(<<)、右移(>>)。
4.引用的概念和應用
(1)引用定義的方式如下:
型別名&引用名=同類型的變數名。
某個變數的引用和這個變數是一回事,相當於該變數的一個別名。注意:
1.定義引用時一定要將其初始化,否則編譯不會通過。通常會用某個變數去初始化引用。
2.初始化後,它就一直引用該變數,不會再引用其他變量了。也可以用一個引用去初始化另一個引用,這樣兩個引用就引用同一個變量了。
3.引用只能引用變數,不能用常量初始化引用。
(2)引用的應用
在C語言中,編寫兩個整形變數值交換的函式;
void swap(int a,int b)
{ int tmp;
tmp=a;a=b;b=tmp
}
int n1,n2;
swap(n1,n2)
在C語言中,這樣寫變數的值不會被交換,因為函式裡改變形參的值不會影響到實參。
C語言中正確的寫法:
void swap(int* a,int* b)
{ int tmp;
tmp=*a;*a=*b;*b=tmp
}
int n1,n2;
swap(&n1,&n2)
C++裡,可以應用引用:
void swap(int& a,int& b)
{ int tmp;
tmp=a;a=b;b=tmp
}
int n1,n2;
swap(n1,n2)
這樣寫變數的值會改變,因為int& a表示引用,引用後兩個變數是等價的,a等價於n1,b等價於n2,改變a的值n1也會跟著變。
(3)常引用
定義引用時,可以在前面加“const”關鍵字,則該引用就成為“常引用”。
如: int n;
const int&r=n;
常引用和普通引用的區別在於不能通過常引用去修改其引用的內容。注意,不是常引用所引用的內容不能被修改,而是不能通過常引用修改其引用的內容。如:
int n=100;
const int&r=n;
r=200; //編譯出錯,不能通過常引用修改其引用的內容
n=300; //沒問題,n的值變為300.
注意:const T&和T&是不同的型別。
(1)T&型別的引用或T型別和變數可以用來初始化const T&型別的引用。
(2)const& T型別的常變數和const T&型別的引用則不能用來初始化T&型別的引用,除非進行強制型別轉換。
5.const關鍵字的用法
(1)定義常量。定義變數時如果在型別名前面加上“const”關鍵字,該變數就變為“常變數”
const int a=5.
常變數的值只能用初始化的方式給出,此後不能被修改。對常變數賦值會導致編譯出錯。
(2)定義指標。定義指標的時候,可以在前面加const關鍵字,則該指標就成為“常量指標”。
const T* P;
常量指標和普通指標的區別在於:不能通過常量指標去修改其指向的內容。注意,不是常量指標所指向的內容不能被修改,只是不能通過常量指標修改其指向的內容,可以用別的辦法修改。
const int* p;
int n=100;
p=&n;
*p=200; //編譯出錯,不能通過常量指標修改其指向的內容。
n=300; //編譯正確,n的值變為300
注意:const T*和T*是不同的型別。T*型別的指標可以賦值給const T*型別的指標,反過來不行,除非進行強制型別轉換。
函式引數為常量指標時,可避免函式內部不小心改變函式引數指標所指地方的內容。
6.動態記憶體分配
【1】. C++裡可以用new運算子實現動態記憶體分配,使得程式可以在執行期間,根據實際需要,要求作業系統臨時分配給自己一片記憶體空間用於存放資料。此種記憶體分配是在程式執行中執行的,而不是在編譯時就確定的,因此稱為“動態記憶體分配”。new運算子的兩種用法如下:
(1)P=new T;
其中,T是任意型別名,P是型別為T*的指標。動態分配出一片大小為sizeof(T)位元組的記憶體空間,並且將該記憶體空間的起始地址賦值給P。例如:
int *P;
p=new int;
*p=5;
(2)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 運算子返回值的型別:new T ,new T[N] 返回值的型別都是T*。
【2】C++裡用delete運算子釋放動態分配的記憶體,new出來的動態記憶體空間一定要用delete運算子進行釋放。用法:
(1) delete 指標; //該指標必須是指向動態分配的記憶體空間的,否則執行時很可能會出錯。例如:
int *p=new int;
*p=5;
delete p;
delete p; //本句會導致程式出錯 因為p指向的空間已經釋放了,p不再是指向動態分配的記憶體空間的指標了。
(2) delete[ ]指標;
例如:
int *p=new int[20];
p[0]=1;
delete[ ]p;
同樣要求,被釋放的指標p必須是指向動態分配的記憶體空間的指標,否則會出錯。
!注意:如果動態分配了一個數組,但是卻用“delete指標”的方式釋放,沒有用[ ],則編譯時沒有問題,執行時也一般不會發生錯誤,但實際上會導致動態分配的陣列沒有被完全釋放。
7.行內函數
函式呼叫是有時間開銷的,如果函式內部本身就沒有幾條語句,執行的時間本來就很短,而且函式被反覆執行很多次,相比之下,呼叫函式所產生的開銷就比較大。
為了減少函式呼叫的開銷,引入了行內函數機制,編譯器處理對行內函數的呼叫語句時,是將整個函式的程式碼插入到呼叫語句處,而不會產生呼叫函式的語句。
行內函數的寫法:在定義函式時,在返回值型別前面加上“inline”關鍵字。例如:
inline int Max(int a,int b)
{
if(a>b)
return a;
return b;
}
行內函數將整個函式體的程式碼插入呼叫語句處,就像整個函式體在呼叫處被重寫了一遍一樣。行內函數比使用普通函式會使最終可執行程式的體積增加。以時間換取空間或增加空間消耗來節省時間,也是計算機學科中常用的辦法。
另外需要注意的是,呼叫行內函數的語句前面,必須已經出現行內函數的定義(即整個函式體),而不能只出現行內函數的宣告。
8.函式過載
一個或多個函式名字相同,然而引數個數或引數型別不相同,這叫做函式的過載。C語言裡沒有函式過載機制,但C++有。
以下三個函式是過載的關係:
int Max(double f1,double f2){}
int Max(int n1,int n2){}
int Max(int n1,int n2,int n3){}
注意:同名函式只有引數不同才算過載,如果兩個同名函式引數表相同而返回值型別不同,不是過載而是重複定義,是補允許的。
9.函式的預設引數
C++中,定義函式的時候可以讓最右邊的連續若干個引數有預設值,那麼呼叫函式的時候,若相應位置不寫引數,引數就是預設值。
如:
void func(int x1,int x2=10,int x3=8){
func(10); //等效於func(10,10,8)
func(10,8);//等效於func(10,8,8)
func(10,,10); //不行,只能最右邊的連續若干個引數預設。
預設引數優點:
(1)編寫函式呼叫語句時可以少輸入引數,尤其在函式引數個數多時能省事兒。
(2)使程式的可擴充性變好,即程式需要增加新功能時改動儘可能少。如果某個寫好的函式需要新增新的引數,而原先那些呼叫該函式的語句,未必需要使用新增的引數,為了避免那些對原先寫好的呼叫語句的修改,就可以使用預設引數。