多檔案結構和編譯預處理命令
C++程式的一般組織結構
•一個源程式可以劃分為多個原始檔:
-
類宣告檔案(.h檔案)
-
類實現檔案(.cpp檔案)
-
類的使用檔案(main()所在的.cpp檔案)
外部變數
•在所有類之外宣告的函式(也就是非成員函式),都是具有檔案作用域的。
•這樣的函式都可以在不同的編譯單元中被呼叫,只要在呼叫之前進行引用性宣告(即宣告函式原型)即可。也可以在宣告函式原型或定義函式時用extern修飾,其效果與不加修飾的預設狀態是一樣的。
namespace
•使用匿名的名稱空間:在匿名名稱空間中定義的變數和函式,都不會暴露給其它的編譯單元。
**namespace** { //匿名的名稱空間
int n;
void f() {
n++;
}
}
•這裡被“namespace { …… }”括起的區域都屬於匿名的名稱空間。
訪問許可權
-
public:所有
-
private:本類,友元
-
protected:本類,派生類,友元
過載
- 過載函式引數要不同(型別,個數或者順序不同)
行內函數
- 為了提高執行效率
- 不要有複雜的結構(迴圈,swich語句)
- 將函式體放在類的宣告中
- 使用inline關鍵字
- 類結構中所在的類說明內部定義的函式是行內函數。
帶預設引數的函式
- 類中宣告的時候帶預設引數,具體實現的時候不帶
建構函式
- 函式名和類名完全相同
- 不寫建構函式時,系統會預設給一個無參的預設建構函式
- 個數大於等於一個
解構函式
- 物件銷燬時被呼叫
- 不能過載,有且只有一個
委託建構函式
- C++11 引入
複製(複製)建構函式
#include <iostream>
using namespace std;
class Point { //Point 類的定義
public: //外部介面
Point(int xx = 0, int yy = 0) { //建構函式
x = xx;
y = yy;
}
Point(const Point &p); //複製建構函式
int getX() {
return x;
}
int getY() {
return y;
}
private: //私有資料
int x, y;
};
//成員函式的實現
Point::Point(const Point &p) {
x = p.x;
y = p.y;
cout << "Calling the copy constructor" << endl;
}
//形參為Point類物件的函式
void fun1(Point p) {
cout << p.getX() << endl;
}
//返回值為Point類物件的函式
Point fun2() {
Point a(1, 2);//臨時物件
return a;
}
//主程式
int main() {
Point a(4, 5); //第一個物件A
Point b = a; //情況一,用A初始化B。第一次呼叫複製建構函式
cout << b.getX() << endl;
fun1(b); //情況二,物件B作為fun1的實參。第二次呼叫複製建構函式
b = fun2(); //情況三,函式的返回值是類物件,函式返回時,呼叫複製建構函式,賦值(此時b不構造)
cout << b.getX() << endl;
cout << b.getY() << endl;
return 0;
}
- 函式返回物件時會增加一個臨時物件,增加開銷(情況三)
移動建構函式
c++11標準
左值:賦值語句左側的物件變數。
右值:賦值語句右側的值,不依附於物件。
float n=6;
float &lr_n=n; //對變數n的左值引用
float &&rr_n=n; //錯誤,不能將右值引用繫結到左值n上
float &&rr_n=n*n; //將乘法結果左值繫結到左值引用
float &lr_n=n*n; //錯誤,不能將左值引用繫結到右值n*n上
標準庫utility中宣告提供了move函式,將左值物件移動成為右值。
float n=10;
float &&rr_n = std::move(n);
default、delete函式(C++11標準)
組合
•類中的成員資料是另一個類的物件。
•可以在已有抽象的基礎上實現更復雜的抽象。
原則:不僅要負責對本類中的基本型別成員資料賦初值,也要對物件成員初始化。
宣告形式:
類名::類名(物件成員所需的形參,本類成員形參)
:物件1(引數),物件2(引數),......
{
//函式體其他語句
}
構造組合類物件時的初始化次序
•首先對建構函式初始化列表中列出的成員(包括基本型別成員和物件成員)進行初始化,初始化次序是成員在類體中定義的次序。
▫成員物件建構函式呼叫順序:按物件成員的宣告順序,先宣告者先構造。
▫初始化列表中未出現的成員物件,呼叫用預設建構函式(即無形參的)初始化
•處理完初始化列表之後,再執行建構函式的函式體。
eg類的組合,線段(Line)類
#include <iostream>
#include <cmath>
using namespace std;
//Point類省略
//類的組合
class Line { //Line類的定義
public: //外部介面
Line(Point xp1, Point xp2);
Line(Line &l);
double getLen() { return len; }
private: //私有資料成員
Point p1, p2; //Point類的物件p1,p2
double len;
};
//組合類的建構函式
Line::Line(Point xp1, Point xp2) : p1(xp1), p2(xp2) {
cout << "Calling constructor of Line" << endl;
double x = static_cast<double>(p1.getX() - p2.getX());
double y = static_cast<double>(p1.getY() - p2.getY());
len = sqrt(x * x + y * y);
}
Line::Line (Line &l): p1(l.p1), p2(l.p2) {//組合類的複製建構函式
cout << "Calling the copy constructor of Line" << endl;
len = l.len;
}
int main() {
Point myp1(1, 1), myp2(4, 5); //建立Point類的物件
Line line(myp1, myp2); //建立Line類的物件
Line line2(line); //利用複製建構函式建立一個新物件
cout << "The length of the line is: ";
cout << line.getLen() << endl;
cout << "The length of the line2 is: ";
cout << line2.getLen() << endl;
return 0;
}
前向引用宣告
class B; //前向引用宣告
class A {
public:
void f(B b);
};
class B {
public:
void g(A a);
};
作用域
//5_1.cpp
#include <iostream>
using namespace std;
int i; //全域性變數,檔案作用域
int main() {
int i = 5; //定義區域性變數i並賦值
{ //子塊1
int i; //區域性變數,區域性作用域
i = 7;
cout << "i = " << i << endl;//輸出7
}
cout << "i = " << i << endl;//輸出5
::i = 10;
cout << "i = " << i << endl;//輸出5
cout <<"i = " << ::i << endl;//輸出10
return 0;
}
靜態資料成員
- ▫用關鍵字static宣告
- 必須在類外定義和初始化,用*(::)來指明所屬的類
eg :具有靜態資料成員的Point類
#include <iostream>
using namespace std;
class Point { //Point類定義
public: //外部介面
Point(int x = 0, int y = 0) : x(x), y(y) { //建構函式
//在建構函式中對count累加,所有物件共同維護同一個count
count++;
}
Point(Point &p) { //複製建構函式
x = p.x;
y = p.y;
count++;
}
~Point() { count--; }
int getX() { return x; }
int getY() { return y; }
void showCount() { //輸出靜態資料成員
cout << " Object count = " << count << endl;
}
private: //私有資料成員
int x, y;
static int count; //靜態資料成員宣告,用於記錄點的個數
};
int Point::count = 0;//靜態資料成員定義和初始化,使用類名限定
int main() { //主函式
Point a(4, 5); //定義物件a,其建構函式回使count增1
cout << "Point A: " << a.getX() << ", " << a.getY();
a.showCount(); //輸出物件個數
Point b(a); //定義物件b,其建構函式回使count增1
cout << "Point B: " << b.getX() << ", " << b.getY();
b.showCount(); //輸出物件個數
return 0;
}
靜態成員函式
沒有this指標
靜態成員函式無法訪問成員變數
#include <iostream>
using namespace std;
class Point { //Point類定義
public: //外部介面
Point(int x = 0, int y = 0) : x(x), y(y) { //建構函式
//在建構函式中對count累加,所有物件共同維護同一個count
count++;
}
Point(Point &p) { //複製建構函式
x = p.x;
y = p.y;
count++;
}
~Point() { count--; }
int getX() { return x; }
int getY() { return y; }
static void showCount() { //輸出靜態資料成員
cout << " Object count = " << count << endl;
}
private: //私有資料成員
int x, y;
static int count; //靜態資料成員宣告,用於記錄點的個數
};
int Point::count = 0;//靜態資料成員定義和初始化,使用類名限定
int main() { //主函式
Point::showCount();
Point a(4, 5); //定義物件a,其建構函式回使count增1
cout << "Point A: " << a.getX() << ", " << a.getY();
a.showCount(); //輸出物件個數
Point b(a); //定義物件b,其建構函式回使count增1
cout << "Point B: " << b.getX() << ", " << b.getY();
b.showCount(); //輸出物件個數
Point::showCount();
return 0;
}
友元
友元不是成員函式
友元使用的原因
結合著類的特性,可知:類具有封裝和資訊隱藏的特性。只有類的成員函式才能訪問類的私有成員,程式中的其他函式是無法訪問私有成員的。非成員函式可以訪問類中的公有成員,但是如果將資料成員都定義為公有的,這又破壞了隱藏的特性。另外,應該看到在某些情況下,特別是在對某些成員函式多次呼叫時,由於引數傳遞,型別檢查和安全性檢查等都需要時間開銷,而影響程式的執行效率。
為了解決上述問題,提出一種使用友元的方案。友元是一種定義在類外部的普通函式,但它需要在類體內進行宣告,為了與該類的成員函式加以區別,在宣告時前面加以關鍵字friend。友元不是成員函式,但是它可以訪問類中的私有成員。友元的作用在於提高程式的執行效率,但是,它破壞了類的封裝性和隱藏性,使得非成員函式可以訪問類的私有成員。
優缺點
利用 friend 修飾符,可以讓一些普通函式 或 另一個類的成員函式 直接對某個類的保護成員和私有成員進行操作,提高了程式的執行效率;同時避免把類的成員都宣告為public,最大限度地保護資料成員的安全。
但是,即使是最大限度地保護資料成員,友元也破壞了類的封裝性。
如果將類的封裝比喻成一堵牆的話,那麼友元機制就像牆上開了一個門。所以使用友元時一定要慎重。
eg友元函式 (計算兩點間的距離)
#include <iostream>
#include <cmath>
using namespace std;
class Point { //Point類宣告
public: //外部介面
Point(int x=0, int y=0) : x(x), y(y) { }
int getX() { return x; }
int getY() { return y; }
friend float dist(Point &a, Point &b);
private: //私有資料成員
int x, y;
};
float dist( Point& a, Point& b) {
double x = a.x - b.x;
double y = a.y - b.y;
return static_cast<float>(sqrt(x * x + y * y));
}
int main() {
Point p1(1, 1), p2(4, 5);
cout <<"The distance is: ";
cout << dist(p1, p2) << endl;
return 0;
}
友元類
宣告
friend class A;
friend void A::print();
eg
class A {
friend class B;
public:
void display() {
cout << x << endl;
}
private:
int x;
}
class B {
public:
void set(int i);
void display();
private:
A a;
};
void B::set(int i) {
a.x=i;//由於B是友元類,故B中函式可訪問A中的x
}
void B::display() {
a.display();
}
友元關係是單向的
上面案例如果宣告B類是A類的友元,B類的成員函式就可以訪問A類的私有和保護資料,但A類的成員函式卻不能訪問B類的私有、保護資料。
共享資料的保護
•對於既需要共享、又需要防止改變的資料應該宣告為常型別(用const進行修飾)。對於不改變物件狀態的成員函式應該宣告為常函式。
-
常物件:必須進行初始化,不能被更新。
const 類名 物件名
class A
{
public:
A(int i,int j) {x=i; y=j;}
...
private:
int x,y;
};
A const a(3,4); //a是常物件,不能被更新
透過常物件只能呼叫它的常成員函式。
-
常成員
用const進行修飾的類成員:常資料成員和常函式成員
- 常函式成員
使用const關鍵字說明的函式。
常成員函式不更新物件的資料成員。
常成員函式說明格式:
型別說明符 函式名(參數列)const;
這裡,const是函式型別的一個組成部分,因此在實現部分也要帶const關鍵字。const關鍵字可以被用於參與對過載函式的區分
常物件只能呼叫常成員函式,並且優先呼叫普通成員函式
- eg const關鍵字可以被用於參與對過載函式的區分
#include<iostream>
using namespace std;
class R {
public:
R(int r1, int r2) : r1(r1), r2(r2) { }
void print();
void print() const;
private:
int r1, r2;
};
void R::print() {
cout << r1 << ":" << r2 << endl;
}
void R::print() const {
cout << r1 << ";" << r2 << endl;
}
int main() {
R a(5,4);
a.print(); //呼叫void print()
const R b(20,52);
b.print(); //呼叫void print() const
return 0;
}
-
eg 常物件只能呼叫常成員函式
#include<iostream> using namespace std; class R { public: R(int r1, int r2) : r1(r1), r2(r2) { } void print(); private: int r1, r2; }; void R::print() { cout << r1 << ":" << r2 << endl; } int main() { R a(5,4); a.print(); //呼叫void print() const R b(20,52); b.print();//出錯 return 0; }
-
常資料成員
#include <iostream> using namespace std; class A { public: A(int i); void print(); private: const int a; static const int b; //靜態常資料成員 }; const int A::b=10; A::A(int i) : a(i) { }//只能這樣初始化常資料成員,不能出現為a賦值的操作 void A::print() { cout << a << ":" << b <<endl; } int main() { /*建立物件a和b,並以100和0作為初值,分別呼叫建構函式,透過建構函式的初始化列表給物件的常資料成員賦初值*/ A a1(100), a2(0); a1.print(); a2.print(); return 0; }
-
常引用:被引用的物件不能被更新。
const 型別說明符 &引用名
#include <iostream>
#include <cmath>
using namespace std;
class Point { //Point類定義
public: //外部介面
Point(int x = 0, int y = 0)
: x(x), y(y) { }
int getX() { return x; }
int getY() { return y; }
friend float dist(const Point &p1, const Point &p2);
private: //私有資料成員
int x, y;
};
float dist(const Point &p1, const Point &p2) {
double x = p1.x - p2.x;
double y = p1.y - p2.y;
return static_cast<float>(sqrt(x * x + y * y));
}
int main() { //主函式
const Point myp1(1, 1), myp2(4, 5);
cout << "The distance is: ";
cout << dist(myp1, myp2) << endl;
return 0;
}
-
常陣列:陣列元素不能被更新
型別說明符 const 陣列名[大小]
-
常指標:指向常量的指標
物件指標
•宣告形式
類名 *物件指標名;
例: Point a(5,10);
Piont *ptr;
ptr=&a;
•透過指標訪問物件成員
物件指標名->成員名
ptr->getx() 相當於 (*ptr).getx();
動態建立物件(new delete)
#include <iostream>
using namespace std;
class Point {
public:
Point() : x(0), y(0) {
cout<<"Default Constructor called."<<endl;
}
Point(int x, int y) : x(x), y(y) {
cout<< "Constructor called."<<endl;
}
~Point() { cout<<"Destructor called."<<endl; }
int getX() const { return x; }
int getY() const { return y; }
void move(int newX, int newY) {
x = newX;
y = newY;
}
private:
int x, y;
};
int main() {
cout << "Step one: " << endl;
Point *ptr1 = new Point;//呼叫預設建構函式
delete ptr1; //刪除物件,自動呼叫解構函式
cout << "Step two: " << endl;
ptr1 = new Point(1,2);
delete ptr1;
Point *ptr = new Point[2]; //建立物件陣列
ptr[0].move(5, 10); //透過指標訪問陣列元素的成員
ptr[1].move(15, 20); //透過指標訪問陣列元素的成員
cout << "Deleting..." << endl;
delete[] ptr; //刪除整個物件陣列
return 0;
}
string類
•常用建構函式
▫string(); //預設建構函式,建立一個長度為0的串
▫string(const char *s); //用指標s所指向的字串常量初始化string類的物件
▫string(const string& rhs); //複製建構函式
•例:
▫string s1; //建立一個空字串
▫string s2 = “abc”; //用常量建立一個初值為”abc”的字串
▫string s3 = s2;//執行複製建構函式,用s2的值作為s3的初值
•常用運算子
▫s + t 將串s和t連線成一個新串
▫s = t 用t更新s
▫s == t 判斷s與t是否相等
▫s != t 判斷s與t是否不等
▫s < t 判斷s是否小於t(按字典順序比較)
▫s <= t 判斷s是否小於或等於t (按字典順序比較)
▫s > t 判斷s是否大於t (按字典順序比較)
▫s >= t 判斷s是否大於或等於t (按字典順序比較)
▫s[i] 訪問串中下標為i的字元
•例:
▫string s1 = "abc", s2 = "def";
▫string s3 = s1 + s2; //結果是"abcdef"
▫bool s4 = (s1 < s2); //結果是true
▫char s5 = s2[1]; //結果是'e'
用getline輸入整行字串
•輸入整行字串
▫用cin的>>運算子輸入字串,會以空格作為分隔符,空格後的內容會在下一回輸入時被讀取
▫用string標頭檔案中的getline可以輸入整行字串,例如:
getline(cin, s2);
•以其它字元作為分隔符輸入字串
▫輸入字串時,可以使用其它分隔符作為字串結束的標誌(例如逗號、分號)
▫把分隔符作為getline的第3個引數即可,例如:
getline(cin, s2, ',');
include <iostream>
#include <string>
using namespace std;
int main() {
for (int i = 0; i < 2; i++) {
string city, state;
getline(cin, city, ',');
getline(cin, state);
cout << "City:" << city << “ State:" << state << endl;
}
return 0;
}