程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C和C++語言學習總結(二)

C和C++語言學習總結(二)

編輯:關於C++

4、函數參數傳遞

C++語言中,函數的參數和返回值的傳遞方式有三種:值傳遞、指針傳遞和引用傳遞.

"值傳遞"的示例程序.由於Func1 函數體內的x 是外部變量n 的一份拷貝,

改變x 的值不會影響n, 所以n 的值仍然是0.

void Func1(int x)
{
x = x + 10;
}

int n = 0;
Func1(n);
cout < < "n = " < < n < < endl; // n = 0

"指針傳遞"的示例程序.由於Func2 函數體內的x 是指向外部變量n 的指

針,改變該指針的內容將導致n 的值改變,所以n 的值成為10.

void Func2(int *x)
{
(* x) = (* x) + 10;
}

int n = 0;
Func2(&n);
cout < < "n = " < < n < < endl; // n = 10

"引用傳遞"的示例程序.由於Func3 函數體內的x 是外部變量n 的引用,x

和n 是同一個東西,改變x 等於改變n,所以n 的值成為10.

void Func3(int &x)
{
x = x + 10;
}

int n = 0;
Func3(n);
cout < < "n = " < < n < < endl; // n = 10

內存分配方式

分配方式 變量類型 分配特點

靜態存儲區域分配 全局變量,static 變量 內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在.

棧分配 函數內局部變量 棧內存分配運算內置於處理器的指令集中,效率很高,但是分配的內存容量有限.

堆分配(亦稱動態內存分配) new ,malloc分配 用malloc 或new 申請任意多少的內存,程序員自己負責在何時用free 或delete 釋放內存.

內存錯誤

內存分配未成功,卻使用了它.

內存分配雖然成功,但是尚未初始化就引用它.

內存分配成功並且已經初始化,但操作越過了內存的邊界. 例如在使用數組時經常發生下標"多1"或者"少1"的操作.特別是在for 循環語句中,循環次數很容易搞錯,導致數組操作越界.

忘記了釋放內存,造成內存洩露.

放了內存卻繼續使用它.

函數的return 語句寫錯了,注意不要返回指向"棧內存"的"指針"或者"引用",因為該內存在函數體結束時被自動銷毀.

程序中的對象調用關系過於復雜,實在難以搞清楚某個對象究竟是否已經釋放了內存,此時應該重新設計數據結構,從根本上解決對象管理的混亂局面.

使用free 或delete 釋放了內存後,沒有將指針設置為NULL.導致產生"野指針".

malloc與new區別

malloc 與free 是C++/C 語言的標准庫函數,new/delete 是C++的運算符.它們都可用於申請動態內存和釋放內存.

對於非內部數據類型的對象而言,光用maloc/free 無法滿足動態對象的要求.對象在創建的同時要自動執行構造函數, 對象在消亡之前要自動執行析構函數.由於malloc/free 是庫函數而不是運算符,不在編譯器控制權限之內,不能夠把執行構造函數和析構函數的任務強加於malloc/free.因此C++語言需要一個能完成動態內存分配和初始化工作的運算符new,以及一個能完成清理與釋放內存工作的運算符delete.注意new/delete 不是庫函數.

5、類重載、隱藏與覆蓋區別

成員函數被重載的特征:

(1)相同的范圍(在同一個類中);

(2)函數名字相同;

(3)參數不同;

(4)virtual 關鍵字可有可無.

覆蓋是指派生類函數覆蓋基類函數,特征是:

(1)不同的范圍(分別位於派生類與基類);

(2)函數名字相同;

(3)參數相同;

(4)基類函數必須有virtual 關鍵字.

#include <iostream.h>
class Base
{
public:
void f(int x) { cout < < "Base::f(int) " < < x < < endl; }
void f(float x) { cout < < "Base::f(float) " < < x < < endl; }
virtual void g(void) { cout < < "Base::g(void)" < < endl;}
void h(float x) { cout < < "Base::h(float) " < < x < < endl;}
void k(float x) { cout < < "Base::k(float) " < < x < < endl;}
};
class Derived : public Base
{
public:
virtual void g(void) { cout < < "Derived::g(void)" < < endl;}
void h(int x) { cout < < "Derived::h(int) " < < x < < endl; }
void k(float x) { cout < < "Derived::k(float) " < < x < < endl;}
};
void main(void)
{
Derived d;
Base*pb = &d;
Derived *pd = &d;
pb->f(42); // Base::f(int) 42 //重載
pb->f(3.14f); // Base::f(float) 3.14 //重載
pb->g(); // Derived::g(void) //覆蓋
pd->g(); // Derived::g(void) //覆蓋
pb->h(3.14f) // Base::h(float) 3.14 //隱藏
pd->h(3.14f) // Derived::h(int) 3 //隱藏
pb->k(3.14f) // Base::k(float) 3.14 //隱藏
pd->k(3.14f) // Derived::k(float) 3.14 //隱藏
}

extern問題

如果C++程序要調用已經被編譯後的C 函數,該怎麼辦?

假設某個C 函數的聲明如下:

void foo(int x, int y);

該函數被C 編譯器編譯後在庫中的名字為_foo,而C++編譯器則會產生像_foo_int_int之類的名字用來支持函數重載和類型安全連接.由於編譯後的名字不同,C++程序不能直接調用C 函數.C++提供了一個C 連接交換指定符號extern"C"來解決這個問題.例如:

extern "C"
{
void foo(int x, int y);
… // 其它函數
}
或者寫成
extern "C"
{
#include "myheader.h"
… // 其它C 頭文件
}
這就告訴C++編譯譯器,函數foo 是個C 連接,應該到庫中找名字_foo 而不是找_foo_int_int.C++編譯器開發商已經對C 標准庫的頭文件作了extern"C"處理,所以我們可以用#include 直接引用這些頭文件.

函數參數的缺省值問題

正確方法:

void Foo(int x=0, int y=0); // 正確,缺省值出現在函數的聲明中
void Foo(int x,int y)
{
...
}

錯誤方法:

void Foo(int x=0, int y=0) // 錯誤,缺省值出現在函數的定義體中
{
...
}

正確方法:

void Foo(int x, int y=0, int z=0);

錯誤方法:

void Foo(int x=0, int y, int z=0);

宏代碼與內聯函數區別

語言支持關系:

C 宏代碼

C++ 宏代碼 內聯函數

宏代碼本身不是函數,但使用起來象函數.預處理器用復制宏代碼的方式代替函數調用,省去了參數壓棧、生成匯編語言的CALL調用、返回參數、執行return 等過程,從而提高了速度.使用宏代碼最大的缺點是容易出錯,預處理器在復制宏代碼時常常產生意想不到的邊際效應.

對於任何內聯函數,編譯器在符號表裡放入函數的聲明(包括名字、參數類型、返回值類型).如果編譯器沒有發現內聯函數存在錯誤,那麼該函數的代碼也被放入符號表裡.在調用一個內聯函數時,編譯器首先檢查調用是否正確(進行類型安全檢查,或者進行自動類型轉換,當然對所有的函數都一樣).如果正確,內聯函數的代碼就會直接替換函數調用,於是省去了函數調用的開銷.這個過程與預處理有顯著的不同,因為預處理器不能進行類型安全檢查,或者進行自動類型轉換.假如內聯函數是成員函數,對象的地址(this)會被放在合適的地方,這也是預處理器辦不到的.

內聯函數使用方法:

關鍵字inline 必須與函數定義體放在一起才能使函數成為內聯,僅將inline 放在函數聲明前面不起任何作用.

正確使用方法:

void Foo(int x, int y);
inline void Foo(int x, int y) // inline 與函數定義體放在一起
{

}

錯誤使用方法:

inline void Foo(int x, int y); // inline 僅與函數聲明放在一起
void Foo(int x, int y)
{

}

6、構造和析構的次序

構造從類層次的最根處開始,在每一層中,首先調用基類的構造函數,然後調用成員對象的構造函數.析構則嚴格按照與構造相反的次序執行,該次序是唯一的,否則編譯器將無法自動執行析構過程.

String函數定義

class String
{
public:
String(const char *str = NULL); // 普通構造函數
String(const String &other); // 拷貝構造函數
~ String(void); // 析構函數
String & operate =(const String &other); // 賦值函數
private:
char *m_data; // 用於保存字符串
};
// String 的析構函數
String::~String(void)
{
delete [] m_data;// 由於m_data 是內部數據類型,也可以寫成delete m_data;
}
// String 的普通構造函數
String::String(const char *str)
{
if(str==NULL)
{
m_data = new char[1]; // 若能加NULL 判斷則更好
*m_data = ' ';
}
else
{
int length = strlen(str);
m_data = new char[length+1]; // 若能加NULL 判斷則更好
strcpy(m_data, str);
}
}
// 拷貝構造函數
String::String(const String &other)
{
int length = strlen(other.m_data);
m_data = new char[length+1]; // 若能加NULL 判斷則更好
strcpy(m_data, other.m_data);
}
// 賦值函數
String & String::operate =(const String &other)
{
// (1) 檢查自賦值
if(this == &other)
return *this;
// (2) 釋放原有的內存資源
delete [] m_data;
// (3)分配新的內存資源,並復制內容
int length = strlen(other.m_data);
m_data = new char[length+1]; // 若能加NULL 判斷則更好
strcpy(m_data, other.m_data);
// (4)返回本對象的引用
return *this;
}

來源於網絡,回歸於網絡.

我的Email:[email protected] QQ:48399956

2008年11月21日

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved