前言
每個人的記憶是有限的,學過的東西很快就會遺忘,因此,在即將升大二之際,對大一學習的C++的基礎語法進行整理歸納,並附上一年裏寫過的一些重要代碼,方便今後回顧!
聲明:本文參考教材提供的網絡學習資料(非常感謝,網址已註明),代碼爲博主本人大一時自己寫的代碼(作業、實驗報告的題目),本人水平有限,代碼不一定完美,歡迎幫忙指正!!!
教材
C++程序設計基礎(第5版)(上)
附帶學習網站:http://cs.scutde.net/Courses/course_10/index.html
其他參考網站:C++ 教程 | 菜鳥教程 https://www.runoob.com/cplusplus/cpp-tutorial.html
目錄
目錄
前言
教材
目錄
正文
預備章節
第一章 基本數據與表達式
第二章 程序控制結構
第三章 函數
第四章 數組
第五章 類與對象
第六章 運算符重載
第七章 繼承
第八章 虛函數與多態性
第九章 模板
第十章 輸入/輸出流
第十一章 異常處理
第1章 基本數據與表達式
1.1 概述
1.2 C++的字符集與詞彙
1.3 C++的基本數據類型與存儲形式
1.4 常量與變量
1.5 內存訪問
1.6 表達式
1.7 數據輸入和輸出 |
第2章 程序控制結構
2.1 選擇控制
2.2 循環控制
2.3 判斷表達式的使用
2.4 轉向語句 |
第3章 函 數
3.1 函數的定義與調用
3.2 函數參數的傳遞
3.3 函數調用機制
3.4 函數指針
3.5 內聯函數和重載函數
3.6 變量存儲特性與標識符作用域
3.7 多文件程序結構
3.8 終止程序執行 |
第4章 數組
4.1 一維數組
4.2 指針數組
4.3 二維數組
4.4 數組作函數參數
4.5 動態存儲
4.6 字符數組與字符串
4.7 string類 |
第5章 類與對象
5.1 結構
5.2 類與對象
5.3 構造函數和析構函數
5.4 靜態成員
5.5 友員 |
第6章 運算符重載
6.1 運算符重載規則
6.2 用成員或友員函數重載運算符
6.3 幾個典型運算符重載
6.4 類類型轉換 |
第7章 繼承
7.1 類之間的關係
7.2 基類和派生類
7.3 基類的初始化
7.4 繼承的應用實例
7.5 多繼承 |
第8章 虛函數與多態性
8.1 靜態聯編
8.2 類指針的關係
8.3 虛函數和動態聯編
8.4 純虛函數和抽象類
8.5 虛函數和多態性的應用 |
第9章 模板
9.1 什麼是模板
9.2 函數模板
9.3 類模板 |
第10章 輸入/輸出流
10.1 流類和流對象
10.2 標準流和流操作
10.3 格式控制
10.4 串流
10.5 文件處理 |
第11章 異常處理( ** )
11.1 C++的異常處理機制
11.2 異常處理的實現 |
|
|
正文
預備章節
1.C++兩種編程方法:
1).用結構化方法編程(函數)
2).用面向對象方法編程(類)
2.程序的編譯執行
編輯——編譯——運行
3.常用軟件環境:Visual Studio、Dev C++、Visual C++等
第一章 基本數據與表達式
1.C++的數據類型
2.引用與指針(難點)
(1)引用
C++允許爲對象定義別名,稱爲"引用"。定義引用的說明語句格式如下。
類型 & 引用名 = 對象名 ;
其中,"&"爲引用說明符。
引用說明爲對象建立引用名,即別名。引用在定義初始化時與對象名綁定,程序中不能對引用重定義。一個對象的別名,從使用方式和效果上,與使用對象名一致。
|
|
|
|
(2)指針
C++中,關鍵字const可以約束對象的訪問性質,使對象值只讀 。
1.指向常量的指針
定義形式:
const 類型 * 指針
或者 類型 const * 指針
圖 1.7 指向常量的指針訪問特性
2.指針常量
指針常量的定義方式:
類型 * const 指針
圖 1.8 指針常量的訪問特性
3.指向常量的指針常量
指向常量的指針常量的定義:
const 類型 * const 指針
或者 類型 const * const 指針
圖 1.9 指向常量的指針常量的訪問特性
4.常引用
冠以const定義的引用,將約束對象用別名方式訪問時爲只讀。常引用的定義方式爲:
const 類型 & 引用名 = 對象名
|
|
|
|
3.基本數據類型
類型名
|
說 明
|
字 節
|
示數範圍,精度
|
char
|
字符型
|
1
|
-128 ~ 127
|
signed char
|
有符號字符型
|
1
|
-128 ~ 127
|
unsigned char
|
無符號字符型
|
1
|
0 ~ 255
|
short [int]
|
短整型
|
2
|
-32768 ~ 32767
|
signed short [int]
|
有符號短整型
|
2
|
-32768 ~ 32767
|
unsigned short [int]
|
無符號短整型
|
2
|
0 ~ 65535
|
int
|
整型
|
4
|
-2147483648 ~ 2147483647
|
signed [int]
|
有符號整型
|
4
|
-2147483648 ~ 2147483647
|
unsigned [int]
|
無符號整型
|
4
|
0 ~ 4294967295
|
long [int]
|
長整型
|
4
|
-2147483648 ~ 2147483647
|
signed long [int]
|
有符號長整型
|
4
|
-2147483648 ~ 2147483647
|
unsigned long [int]
|
無符號長整型
|
4
|
0 ~ 4294967295
|
float
|
單精度浮點型
|
4
|
-3.4 × 10 38 ~ 3.4 × 10 38 ,約 6 位有效數字
|
double
|
雙精度浮點型
|
8
|
-1.7 × 10 308 ~ 1.7 × 10 308 ,約 12 位有效數字
|
long double
|
長雙精度浮點型
|
8
|
-3.4 × 10 4932 ~ 1.1 × 10 4932 ,約 15 位有效數字
|
|
|
|
|
4.C++常用的轉義符
名 稱
|
字符形式
|
值
|
空字符 (Null) |
\0 |
0X00 |
換行 (NewLine) |
\n |
0X 0A |
換頁 (FormFeed) |
\f |
0X 0C |
回車 (Carriage Return) |
\r |
0X0D |
退格 (BackSpasc) |
\b |
0X08 |
響鈴 (Bell) |
\a |
0X07 |
水平製表 (Horizontal Tab) |
\t |
0X09 |
垂直製表 (Vertical Tab) |
\v |
0X0B |
反斜槓 (backslash) |
\\ |
0X 5C |
問號 (question mark ) |
\? |
0X 3F |
單引號 (single quote) |
\ ′ |
0X27 |
雙引號 (double quote) |
\ 〞 |
0X22 |
|
|
|
|
5.運算符
優先級
|
運算符
|
功 能
|
結合性
|
1 |
()
|
改變優先級
|
左→右 |
::
|
作用域運算
|
[]
|
數組下標
|
. ->
|
成員選擇
|
.* ->*
|
成員指針選擇
|
2 |
++ --
|
自增,自減
|
右→左 |
&
|
取地址
|
*
|
取內容
|
!
|
邏輯反
|
~
|
按位反
|
+ -
|
取正,取負(單目運算)
|
()
|
強制類型
|
sizeof
|
求存儲字節
|
new delete
|
動態分配,釋放內存
|
3 |
* / %
|
乘,除,求餘
|
左→右 |
4 |
+ -
|
加,減(雙目運算)
|
5 |
<< >>
|
左移位,右移位
|
6 |
< <= > >=
|
小於,小於等於,大於,大於等於
|
7 |
== !=
|
等於,不等於
|
左→右 |
8 |
&
|
按位與
|
9 |
^
|
按位異或
|
10 |
|
|
按位或
|
11 |
&&
|
邏輯與
|
12 |
||
|
邏輯或
|
13 |
?:
|
條件運算
|
右→左 |
14 |
= += -= *= /= %= &= ^=
|
賦值,複合賦值
|
15 |
,
|
逗號運算
|
左→右 |
|
|
|
|
6.表達式
1)算術表達式
2)關係表達式
3)邏輯表達式
4)賦值表達式
5)條件表達式
6)逗號表達式
第二章 程序控制結構
(一) 選擇語句
1.if語句
(1)一個分支的if語句:if (表達式) 語句;
(2)if (表達式) 語句1;
else 語句2;
(3)if (表達式) 語句1;
else if(表達式) 語句2;
else 語句3;
2.switch語句
switch語句形式爲:
switch( 表達式 )
{ case 常量表達式1 :語句1 ;
case 常量表達式2 :語句2 ;
:
case 常量表達式n :語句n;
[ default : 語句n+1 ;]
}
|
switch語句的執行流程
△要在某個語句中斷,需使用break語句!
|
|
|
|
|
(二)循環語句
1.while語句
(1)do-whlie
(2)while-do
2.for語句
for(表達式1;表達式2;表達式3)
{
循環體;
}
(3)判斷表達式的使用
1.算術表達式用於判斷
算術表達式表達一個結果值,如果這個結果值可以用於0值或非0值判斷,則可以直接用作判斷表達式。
2.賦值表達式用於判斷
賦值表達式的值是被賦值變量的值,用於判斷表達式中,首先完成賦值運算,然後以被賦值變量的值作判斷。
3.對輸入作判斷
可以用組合鍵Ctrl_z(同時按Ctrl和z鍵)結束cin輸入。cin的輸入函數返回一個0值。
|
|
|
|
(4)轉向語句
①break ②continue ③return ④goto
第三章 函數
1.函數
自定義函數的形式一般爲:
類型 函數名 ( [ 形式參數表 ] )
{
語句序列
}
函數定義的第一行是函數首部(或稱函數頭);以花括號相括的語句序列爲函數體。
"形式參數表"的一般形式爲:
類型 參數1 ,類型 參數2 ,… ,類型 參數n
|
函數調用的一般形式爲:
函數名 ( [ 實際參數表 ] )
可以用兩種形式調用:
(1)函數語句
(2)函數表達式
|
|
|
|
|
函數原型
|
說明
|
int abs( int n );
|
n 的絕對值
|
double cos( double x );
|
x (弧度)的餘弦
|
double exp( double x );
|
指數函數 e x
|
double fabs( double x );
|
x 的絕對值
|
double fmod( double x , double y );
|
x / y 的浮點餘數
|
double log( double x );
|
x 的自然對數(以 e 爲底)
|
double log10( double x );
|
x 的對數(以 10 爲底)
|
double pow( double x , double y );
|
x 的 y 次方( x y )
|
double sin( double x );
|
x (弧度)的正弦
|
double sqrt( double x );
|
x 的平方根
|
double tan( double x );
|
x (弧度)的正切
|
|
|
|
2.傳值參數
(1)值傳遞機制
實際參數的表達式的值複製到由對應的形參名所標識的一個對象中,作爲形參的初始值。
(2)形式參數類型:
①指針參數(指針類型):被調用函數可以在函數體內通過形參指針簡介訪問實參所指對象。
!!!使用const限定指針,保護實參對象
#include <iostream.h>
int func( const int * p)
{ int a = 10 ;
a += *p ;
//*p = a ; 錯誤,不能修改const對象
// p = &a ; 錯誤
return a ;
}
void main()
{ int x = 10 ;
cout << func( &x ) << endl ;
}
②引用參數(引用類型):形式參數名作爲引用(別名)綁定與實際參數標識的對象。
!!!使用const引用參數
#include<iostream.h>
#include<iomanip.h>
void display( const int & rk )
{
cout << rk << " :\n";
cout<< "dec : " << rk << endl;
cout<< "oct : " << oct << rk << endl;
cout<< "hex : " << hex << rk << endl;
}
void main()
{ display( 4589 ) ; }
3.函數返回類型
C++函數可以通過return語句也可以返回表達式的執行結果。return語句的一般格式爲:
return ( 表達式 );
1.返回基本類型
如果函數定義的返回類型爲基本數值類型,執行return語句時,首先計算表達式的值,然後匿名對象,把數值帶回函數的調用點。
2.返回指針類型
函數被調用之後返回一個對象的指針值(地址表達式)。指針函數的函數原型一般爲:
關聯類型 * 函數名( 形式參數表 );
【例3-15】定義一個函數,返回較大值變量的指針。
3.返回引用類型
C++函數返回對象引用時,不產生返回實際對象的副本,返回時的匿名對象是實際返回對象的引用。
【例3-16】定義一個函數,返回較大值變量的引用。
【例3-17】輸入一系列正整數和負整數,以0結束,統計其中正整數和負整數的個數。
|
|
|
|
4.函數的地址
1)函數指針
函數定義後,函數名錶示函數代碼在內存的直接地址。我們可以用一個指針變量獲取函數的地址,通過指針變量的間址方式調用函數。指向函數的指針變量簡稱爲函數指針。
1.函數的類型
函數的類型是指函數的接口,包括函數的參數定義和返回類型。
函數類型定義的一般形式爲:
類型 函數類型 ( 形式參數表 ) ;
其中,"函數類型"是用戶定義標識符。
2.函數指針
要定義指向某一類函數的指針變量,可以用以下兩種說明語句:
類型 ( * 指針變量名 )( 形式參數表 ) ;
或 函數類型 * 指針變量名 ;
還可以用關鍵字typedef定義指針類型。函數指針類型定義的一般形式爲:
typedef 類型( * 指針類型 )( 形式參數表 ) ;
或 typedef 函數類型 * 指針類型 ;
3.用函數指針調用函數
使用函數指針調用函數的一般形式爲:
( * 指針變量名 )( 實際參數表 )
或 指針變量名 ( 實際參數表 )
【例3-24】用函數指針調用不同函數。
當函數指針作爲函數參數時,可以傳遞函數的地址,通過參數調用不同函數。
【例3-25】使用函數指針參數調用函數。該程序與例3-24的功能相同。
【例3-26】使用函數名作爲函數參數,調用庫函數sin(x)和cos(x),計算指定範圍內間隔爲0.1的函數值之和。
2)函數重載
C++允許定義多個同名函數,每個函數有不同的參數集,稱爲函數重載。
|
|
|
|
5.遞歸函數
遞歸定義能夠用有限的語句描述一個無窮的集合。一個函數體中出現調用自身的語句,稱爲直接遞歸調用。函數體中調用另一個函數,而該函數又反過來調用原函數,稱爲間接遞歸調用。
【例3-19】使用遞歸函數編程序求n!。
遞歸形式定義階乘:
遞歸函數執行由遞推和迴歸兩個過程完成。假如執行main函數時,輸入n值爲3,則函數調用fact(3)的遞推和迴歸過程如圖3.7所示。
圖 3.7 函數調用fact(3)的遞推過程和迴歸過程
【例3-20】求正整數a和b的最大公約數。
a和b的最大公約數,就是能同時整除a和b的最大整數。從數學上知道,求a和b的最大公約數等價於求b與(a % b)的最大公約數。具體的算法可以用遞歸公式表示爲:
【例3-21】斐波那契數列。
斐波那契(Fibonacci)數列的第1項爲0,第2項爲1,後續的每一項是前面兩項的和。數列兩項的比例趨於一個常量:1.618…,稱爲黃金分割。數列形如
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, …
斐波那契數列可以用遞歸定義:
Fibonacci(0) = 0
Fibonacci(1) = 1
Fibonacci(n) = Fibonacci(n-1) + Fibonacci(n-2)
圖3.8表示fibonacci函數求fibonacci(3)的值,圖中將fibonacci縮寫爲fib。
圖 3.8 fibonacci數列的遞歸調用
【例3-22】漢諾塔問題。
傳說印度的主神梵天做了一個塔,它在一個黃銅板上插3根寶石針,其中一根針上從上到下按從小到大的順序串上了64個金片。梵天要求僧侶們把金片全部移動到另一根針上去,規定每次只能移動一片,且不許將大片壓在小片上。移動時可以藉助第三根針暫時存放盤子。梵天說如果這64片金片全部移至另一根針上時,世界就會在一聲霹靂之中毀滅。圖3.9是8個盤子的漢諾塔示意。
圖 3.9 8個盤子的漢諾塔
|
|
|
|
|
|
第四章 數組
1.簡介
·從程序員看,數組是相關數據對象集的一種組織形式。數組以線性關係組織對象,對應於數學的向量、矩陣的概念。數組結構是遞歸的,一個 n 維數組的每個元素是 n-1 維數組。數組元素以下標表示在它數組中的位置,稱爲下標變量。使用指針數組,可以管理複雜的對象,例如字符串、函數以及類對象。
· 從機器系統看,數組佔有一片連續的存儲空間。數組名錶示這片存儲空間的首地址。一旦說明一個數組,不帶下標的數組名是一個常指針。可以用下標變量形式和指針形式訪問已說明的內存空間。
· 使用 new和delete算符能夠動態地申請和收回內存空間。申請的存儲空間同樣用下標形式和指針形式訪問。
· C語言沒有字符串類型,但在輸入輸出操作中可以把字符數組名和字符指針作爲“字符串變量”使用,實現對字符串的“名”訪問。
· string是C++預定義的類,它提供了對字符串更安全和方便的操作。
· 排序、查找、比較、複製等是數組的常用操作。
|
|
2.數組的定義和初始化及訪問
一維數組的說明格式爲:
類型 標識符[表達式];
例如,有以下說明:
int a[10] ; //長度爲10的整型數組
double b[5] ; //長度爲5的浮點型數組
char s['a'] ; //長度爲97的字符型數組
二維數組的說明格式爲:
類型 數組名 [表達式1][表達式2];
例如,有以下說明:
int a[3][4] ; //3行4列的整型數組
double b[10][10] ; //10行10列的浮點型數組
char s[40][40] ; //40行40列的字符型數組
C++的高維數組在內存中以高維優先的方式存放。具體如圖4.6所示。
高維數組可以按照兩種方式初始化。
例如,
int am[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };
或 int am[2][3] = { 1, 2, 3, 4, 5, 6 };
一維數組的訪問形式:下標方式和指針方式。
1.以下標方式訪問數組
下標方式引用數組元素格式:
數組名[表達式]
其中:"表達式"表示數組元素的下標,要求爲整型表達式。
2.以指針方式訪問數組
數組名是數組的指針,即數組的首地址。
例如,說明數組
int a[5];
若內存分配如圖4.2所示,則a的值是地址00B4(十六進制數)。a的偏移量以數組元素類型長度爲單位,a+1的值是00B8,a+2的值是00BC等。
二維數組也可以用下標方式和指針方式訪問。
1. 以下標方式訪問二維數組
二維數組元素帶有兩個下標表達式:
數組名 [表達式1][表達式2]
2. 以指針方式訪問二維數組
以指針方式訪問二維數組,可以從一維數組的結構推導出來。我們還是以int a[2][3]爲例說明。從圖4.7中看到,a是由元素a[0]、a[1]、a[2]組成的一維數組,所以,a是a[0]、a[1]、a[2]的首地址(指針),即:
a == &a[0] a+1 == &a[1] a+2 == &a[2]
*a == a[0] *(a+1) == a[1] *(a+2) == a[2]
對於二維數組,不帶下標的數組名是一個邏輯上的二級指針,所指對象是行向量,偏移量是一行元素的存儲長度。帶一個下標的數組名是一級指針,偏移量是一個元素的長度,它所指的對象的數組元素。
3.動態數組
C++使用new和delete操作符動態分配存儲空間和動態釋放已分配的存儲空間。new和delete的一般語法形式爲:
指針變量 = new 類型(初始化值表)
指針變量 = new 基本類型 [長度]
delete 指針變量
delete [] 指針變量
new操作符返回所分配空間的首地址。由new分配的堆空間沒有名字,只能通過間址方式訪問。
delete收回由"指針變量"所指的內存空間。
例如,以下語句申請如圖4.11所示的內存空間:
int *p1 = new int; //動態分配一個整型單元
char *p2 = new char; //動態分配一個字符型單元
float *p3 = new float; //動態分配一個浮點型單元
int *p4 = new int [4] ; //動態分配整型數組
使用完畢的內存,應該用delete釋放:
delte p1;
delete p2;
delete p3;
delete []p4;
|
|
|
|
//定義動態數組:
//一維:
類型 *a=nullptr;
a = new 類型[n];
//二維:
類型**a=new 類型*[n];
for(int i=0;i<n;++i)
{
a[i]=new 類型[m]; //m可爲常量或變量,[m]可不寫,也可寫爲(y),y爲常數,給數組賦值初始化
}
4.使用數組處理字符串
4.6.1 字符串存儲
1.用字符數組存放字符串
例如:
char str[10]
表示str是一個字符型數組,可以存放10個字符。爲了表示一個字符串的結束位置,可以用'\0'作標誌。
說明一個字符數組時,有不同的初始化方式:
① 逐個字符對數組元素賦初始值:
char str1[10] = { 'S', 't', 'u', 'd', 'e', 'n', 't' };
② 用串常量初始化
char str2[10] = { "Student" };
char str3[] = { "Student" };
或者省略{ }:
char str3[] = "Student" ;
2.用字符指針管理串
字符指針作爲串地址,爲管理字符串提供了方便。
例如:
char *string = "Student";
一個一維數組可以存放一個字符串,二維數組可以對串進行管理。例如,
char *name[5] = { "Chen", "Li", "Zhang", "Huang", "He" };
存儲狀態如圖4.12所示,name的每一個元素是一個字符串的首地址。
圖 4.12 用指針數組管理字符串
4.6.2 字符串的訪問
當字符串以普通數組形式管理時,訪問方法與前面討論過的數組訪問形式一致。
用cin或cout輸入輸出字符串時,字符數組名和字符指針可以像一個"串變量名"那樣使用。
【例4-22】字符數組的訪問。
【例4-23】用字符指針對字符串的輸入輸出。
【例4-24】包含空格的字符串輸入輸出。
【例4-25】測試字符輸出。
【例4-26】指針數組表示的字符串。
【例4-27】串的賦值。
4.6.3 字符串處理函數
C++提供一系列對字符串操作的相關函數。這些函數在string.h的頭文件中聲明。下面介紹一些常用的字符串處理函數的原型、功能和使用方法。
1. 字符串長度函數
原型 int strlen ( const char * s );
功能 返回字符指針s所指的字符串有效長度。
【例4-28】測試字符串長度。
2. 字符串複製 函數
原型 char * strcpy ( char *s1 , const char * s2 );
功能 將s2所指的字符串複製到s1所指的字符數組中,函數返回值是s1串的地址。
【例4-29】複製字符串。
3.字符串連接函數
原型 char * strcat( char * s1 , char * s2 ) ;
功能 把s2所指的字符串添加到s1所指的字符串之後。函數返回s1串的地址。
【例4-30】字符串連接。
4.字符串比較函數
原型 int strcmp( const char * s1 , const char * s2 );
int strncmp( const char * s1 , const char * s2 , int n );
功能 以字典順序方式比較兩個字符串是否相等。如果兩個串相等,函數返回值爲0;如果s1串大於s2串,返回值大於0;如果s1串小於s2串,返回值小於0。函數strcmp用於對兩個串的完全比較;函數strncmp用於比較兩個串的前n個字符。
【例4-31】查找姓名。
【例4-32】字符串排序。
5.輸入串
用cin流輸入字符串時,C++把鍵盤操作的空格或回車都視爲串結束,因此無法輸入帶空格的字符串。C語言的gets函數接受鍵盤輸入的空格,以回車作結束。
puts函數輸出字符串。gets和puts函數在stdio.h文件聲明。
【例4-33】輸入帶空格的字符串。
|
|
|
|
5.數組作函數參數
當數組元素作函數參數時,性質與簡單變量相同;當數組名作參數時,實現地址傳送。
4.4.1 向函數傳送數組元素
數組元素是下標變量,可以用不同形式參數向函數傳遞。
【例4-10】數組元素作傳值參數。
【例4-11】數組元素作引用參數。
4.4.2 數組名作函數參數
當數組名作爲函數參數時,C++做傳址處理。
【例4-12】數組名作函數參數。
這是因爲實參是編譯時建立的數組,形式參數是一個指針類型的臨時變量,存放實參數組的地址(見圖4.9 所示)。
圖 4.9 實參數組和形參數組
【例4-13】修改形參數組指針。
【例4-14】數組的降維處理。
4.4.3 應用舉例
排序是計算機程序設計中的一種重要算法。排序方法有很多,這裏僅介紹兩種簡單排序法:選擇排序和冒泡排序法。
【例4-15】選擇排序。
算法可以描述爲:
for ( i = 0 ; i < n - 1 ; i ++ )
{ 從a[i]到a[n-1]找最小元素a[t]
把a[t]與a[i] 交換
}
細化尋找最小元素算法。每一趟尋找中,設一個變量t,記錄當前最小元素的下標:
for( j = i+1; j<n; j++ )
if( a[j]<a[t] ) t = j;
對數組一趟搜索完成後,找到當前最小值a[t],然後執行a[i]與a[t]交換。
程序用隨機函數初始化數組。
【例4-16】冒泡排序。
冒泡排序法的過程是相鄰元素比較。圖4.10展示了一個冒泡排序實例。
圖 4.10 冒泡排序示例
【例4-17】矩陣相乘。
求兩矩陣的乘積C = A×B。設A、B分別爲m×p和p×n的矩陣,則C是 m×n的矩陣。按矩陣乘法的定義有:
|
|
|
|
第五章 類與對象
1.簡介
·結構類型用struct定義,是用戶自定義數據類型,由不同類型的數據成員組成。結構變量在內存佔有一片連續的存儲區間。結構變量成員用圓點運算符和箭頭運算符訪問。
·鏈表是一種重要的動態數據結構。動態數據的組織特點是可以在程序運行時創建或撤消數據元素。爲了描述動態數據結構中元素之間的關係,數據元素類型定義必須包含表示數據關係的指針。我們詳細討論了最簡單的動態數據結構——單向鏈表的操作。
·類類型是結構類型的拓展,通常用關鍵字class定義。類是數據成員和成員函數的封裝。類的實例稱爲對象。
·數據成員是類的屬性,可以爲各種合法的C++類型,包括類類型。
·成員函數用於操作類的數據或在對象之間發送消息。
·類成員由private, protected, public決定訪問特性。public成員集稱爲類的接口。不能在類的外部訪問private成員。
·構造函數是特殊的成員函數,在創建和初始化對象時自動調用。析構函數則在對象作用域結束時自動調用。
·重載構造函數和複製構造函數提供了創建對象的不同初始化方式。當一個對象擁有的資源是由指針指示的堆時,必須定義深複製方式的複製構造函數。
·靜態成員是局部於類的成員,它提供一種同類對象的共享機制。靜態數據成員在編譯時建立並初始化存儲空間。靜態數據成員和靜態成員函數依賴於類而使用,與是否建立對象無關。
·友員是類對象操作的一種輔助手段。一個類的友員可以訪問該類各種性質的成員。
·從編譯器的觀點看,類是一個程序包。定義什麼類成員和如何聲明成員的訪問性質,取決於問題的需要。 |
|
2.結構struct
1.定義結構
結構類型以關鍵字struct標識,結構說明語句形式爲:
struct <標識符>
{ 類型 成員1 ;
類型 成員2 ;
…
類型 成員n ;
} ;
例如,定義職工檔案的結構類型:
struct Employee1
{ char name[10] ;
long code ;
double salary ;
char *address ;
char phone[20] ;
} ;
2.訪問結構
對結構變量成員訪問用圓點運算符:
結構變量名 . 成員
【例5-1】訪問結構變量。
如果用指針訪問結構,訪問形式爲:
*(指針). 成員
或 指針 -> 成員
【例5-2】用指針訪問結構。
【例5-3】結構變量賦值。
|
|
|
3.鏈表
程序設計中處理的數據對象每個元素之間往往存在某種關係,要求在數據表示上,不但要存放基本的信息,還要表示與其它元素的連接。這一節僅從程序設計語言的角度,通過最簡單的鏈表結構,介紹對數據關係的存儲和操作。
1.動態鏈表存儲 最簡單的數據組織形式是線性表。表中除了第一個和最後一個元素外,每一個元素都有一個前驅和一個後繼元素。若數據元素是在程序運行中需要動態插入或刪除,數組操作顯然不方便。可以用如圖5.1 所示的"單向鏈表"表示。
圖 5.1 單向鏈表
圖5.1的鏈表可用以下的結構類型存放數據: struct node { char name[20]; float salary; node * next;
};
2.建立和遍歷鏈表
設有說明: struct node
{ int data; node * next; }; node *head, *p;
建立鏈表的過程可以描述爲:
生成頭結點; while(未結束)
{ 生成新結點; 把新結點插入鏈表;
}
圖5.2所示操作建立第一個結點:
圖 5.2 建立第一個結點
然後建立後續結點。圖5.3所示操作把生成結點插入表尾:
圖 5.3 建立後續結點
一旦生成頭結點,頭指針就不應該移動。p稱爲跟蹤指針。
【例5-4】建立單向鏈表。
【例5-5】遍歷鏈表。
3.插入結點
以下討論各種情況的插入。
(1)在表頭插入結點
在表頭插入結點是要使被插結點成爲第一個結點,步驟是: ① 生成新結點; ② 把新結點連接上鍊表; ③ 修改表頭指針。
具體操作見圖5.4。
圖 5.4 在表頭插入結點
(2)在*p之後插入*s 在*p之後插入結點與在表頭插入結點的操作相似,不過首先要查找p的位置(見圖5.5)。
圖 5.5 在*p之後插入*s
(3)在*p之前插入*s 在*p之前插入*s,需要找到*p的前驅結點的地址,所以查找過程要定位於*p的前驅。圖5.6的操作設q指針指向*p的前驅結點。
圖 5.6 在*p之前插入*s
【例5-6】用插入法生成一個有序鏈表。 爲了找到第一個大於num的結點時完成前插,程序用雙跟蹤指針查找。q是p的前驅指針,開始查找時指針的初始狀態(圖5.7)是:
圖 5.7 開始查找
若在鏈表中找不到大於num的結點,說明num是當前最大值,應該插入表尾。這時,指針的狀態(圖5.8)如下:
圖 5.8 查找結束
4.刪除結點
刪除結點也要根據結點的位置作不同處理,還要注意釋放被刪結點。
(1)刪除頭結點 操作見圖5.9。
圖 5.9 刪除頭結點
(2)刪除 *p 刪除結點*p,需要知道其前驅結點指針。操作見圖5.10。
圖 5.10 刪除結點 *p
【例5-7】從頭指針爲head的鏈表中刪除值等於key的結點。
【例5-8】約瑟夫(Jonsephus)問題。
如圖5.11所示,n個人圍成一個環,從第i個開始,由1至interval不斷報數,凡報到interval的出列,直到環空爲止。出列的人按先後順序構成一個新的序列。例如,n=8,i=2,interval=3,則輸出序列爲: 4 7 2 6 3 1 5 8
編程模擬這個遊戲。
程序的運行情況如圖5.12所示。
圖 5.12 約瑟夫環操作示意
|
|
|
4.類
類是在結構的基礎上發展而來的。類定義解決了對數據和操作的封裝及對對象的初始化。除此之外,面向對象方法的還支持繼承、多態機制,爲大型軟件的複雜性和可重用性提供了有效的途徑。
1)定義類和對象
C++中,屬性以數據的存儲結構實現,稱爲類的數據成員;方法用函數實現,稱爲成員函數。它們都是類的成員。 C++中,類定義的說明語句一般形式爲:
class <類名>
{ public:
公有段數據成員和成員函數 ;
protected:
保護段數據成員和成員函數 ;
private:
私有數據成員和成員函數 ;
} ;
其中,class是定義類的關鍵字。 類成員用關鍵字指定不同的訪問特性,決定其在類體系中或類外的可見性。 關鍵字private用於聲明私有成員。 protected聲明保護成員。 public聲明公有成員。
例如,一個日期類的定義如下:
class Date
{ public:
void SetDate( int y, int m, int d );
int IsLeapYear() ;
void PrintDate() ;
private:
int year, month, day ;
} ;
2)this指針
3)構造函數和析構函數
a.構造函數和析構函數的原型是:
類名::類名( 參數表 );
類名 ::~ 類名();
構造函數和析構函數不能定義在私有部分。
b.帶參數的構造函數
c.重載構造函數
d.複製構造函數
4)靜態數據成員static
5)友元函數與友元類
|
|
|
|
//題目:定義向量並實現向量的簡單運算(經典例題)
// P275Ex3改模板類(多維向量).cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
//代碼涉及模板類(後續內容)
#include "pch.h"
#include <iostream>
using namespace std;
template <typename T>
class Vector
{
public:
Vector(int msize);
Vector(int msize, T*p);
Vector(const Vector&); //複製構造函數***
~Vector();
template <typename T>
friend Vector<T> operator+(const Vector<T> &A, const Vector<T> &B);
template <typename T>
friend Vector<T> operator-(const Vector<T> &A, const Vector<T> &B);
template <typename T>
friend Vector<T> operator*(const Vector<T> &A, const Vector<T> &B);
Vector<T>& operator=(const Vector<T>A); //加const
template <typename T>
friend istream& operator>>(istream& input, Vector<T>& A);
template <typename T>
friend ostream& operator<<(ostream &output, const Vector<T>& A); //一定要加const
public:
T *x;
int size;
};
template <typename T>
Vector<T>::Vector(int msize)
{
size = msize;
x = new T[size];
for (int i = 0; i < size; ++i)
{
x[i] = 0;
}
}
template <typename T>
Vector<T>::~Vector()
{
delete[]x;
}
template <typename T>
Vector<T>::Vector(int msize,T*p)
{
size = msize;
x = new T[size];
for (int i = 0; i < size; ++i)
{
x[i] = p[i];
}
}
template <typename T>
Vector<T>::Vector(const Vector&A)
{
delete []x;
x = new T[A.size];
for (int i = 0; i < A.size; ++i)
{
x[i] = A.x[i];
}
}
template <typename T>
Vector<T> operator+(const Vector<T> &A, const Vector<T> &B)
{
T* a = new T[A.size];
for (int i = 0; i < A.size; ++i)
{
a[i] = A.x[i] + B.x[i];
}
return Vector<T>(A.size,a);
}
template <typename T>
Vector<T> operator-(const Vector<T> &A, const Vector<T> &B)
{
T* a = new T[A.size];
for (int i = 0; i < A.size; ++i)
{
a[i] = A.x[i] - B.x[i];
}
return Vector<T>(A.size, a);
}
template <typename T>
Vector<T> operator*(const Vector<T> &A, const Vector<T> &B)
{
T* a = new T[A.size];
for (int i = 0; i < A.size; ++i)
{
a[i] = A.x[i] * B.x[i];
}
return Vector<T>(A.size, a);
}
template <typename T>
Vector<T>& Vector<T>::operator=(const Vector<T>A) //複製構造函數
{
for (int i = 0; i < A.size; ++i)
{
x[i] = A.x[i];
}
return *this;
}
template <typename T>
istream& operator>>(istream& input, Vector<T>& A)
{
for (int i = 0; i < A.size; ++i)
{
input >> A.x[i];
}
return input;
}
template <typename T>
ostream& operator<<(ostream &output, const Vector<T>& A)
{
output << '(';
for (int i = 0; i < A.size; ++i)
{
output << A.x[i];
if (i != A.size - 1) output << ",";
}
output << ')';
return output;
}
int main()
{
int size;
cout << "請輸入向量維數:";
cin >> size;
Vector<int> A(size);
Vector<int> B(size);
cout << "請輸入向量A:";
cin >> A ;
cout << "請輸入向量B:";
cin >> B;
cout << "A=" << A << endl;
cout << "B=" << B << endl;
cout << "A+B=" << A+B << endl;
cout << "A-B=" << A - B << endl;
cout << "A*B=" << A * B << endl;
Vector<int> D(size);
D = A + B; //必須寫複製構造函數才能這樣寫
cout <<"D=A+B="<< D;
}
第六章 運算符重載
1.基本語法
6.1.1 重載運算符的限制
C++語言中大部分預定義的運算符都可以被重載。以下列出可以重載的運算符:
+ - * / % ^ & | ~
! = < > += -= *= /= %=
^= &= |= << >> >>= <<= == !=
<= >= && || ++ -- ->* ' ->
[] () new delete new[] delete[]
只有幾個運算符不能被重載:
. .* :: ?: sizeof
重載運算符函數可以對運算符做出新的解釋,即定義用戶所需要的各種操作。但運算符重載後,原有的基本語義不變。
6.1.2 運算符重載的語法形式
運算符函數是一種特殊的成員函數或友員函數。成員函數的語法形式爲
類型 類名 :: operator op ( 參數表 )
{
//相對於該類定義的操作
}
通常重載算符用成員函數或友員函數。它們的關鍵區別在於成員函數具有this指針,而友員函數沒有this指針。
1.一元運算符
一元運算符不論前置或後置,都要求一個操作數:
Object op
或 op Object
當重載爲成員函數時,參數表爲空。當重載爲友員函數時,操作數由參數表的參數提供。
2.二元運算符
任何二元運算符要求左、右操作數:
ObjectL op ObjectR
當重載爲成員函數時,左操作數由對象ObjectL通過this指針傳遞,右操作數由參數ObjectR傳遞。
重載爲友員函數時,左右操作數都由參數傳遞。
6.2.1 用成員函數重載算符
當一元運算符的操作數,或者二元運算符的左操作數是該類的一個對象時,重載算符函數一般定義爲成員函數。
6.2.2 用友員函數重載
當函數左右操作數類型不同時,用友員函數重載運算符,因爲左右操作數都由參數傳遞,可以通過構造函數實現數據類型隱式轉換。
當一個運算符的操作需要修改類對象狀態時,應該以成員函數重載。例如,需要左值操作數的運算符(如 =,*=,++ 等)應該用成員函數重載。如果以友員函數重載,可以使用引用參數修改對象。
當運算符的操作數(尤其是第一個操作數)希望有隱式轉換,則重載算符時必須用友員函數或普通函數。 C++中不能用友員函數重載的運算符有
= () [] ->
|
|
|
6.3.1 重載 ++ 與 --
自增和自減運算符有前置和後置兩種形式。每個重載運算符的函數都必須有明確的特徵,使編譯器確定要使用的版本。C++規定,前置形式重載爲一元運算符函數,後置形式重載爲二元運算符函數。
【例6-4】例6-2中使用了成員函數重載++和-運算符。本例用友員函數重載++運算符。
6.3.2 重載賦值運算符
賦值運算符重載用於對象數據的複製。重載函數原型爲:
類型 & 類名::operator= ( const 類名 & ) ;
【例6-5】定義Name類的重載賦值函數。
運算符函數operator= 必須重載爲成員函數,而且不能被繼承。
6.3.3 重載運算符[]和()
運算符"[]"和"()"只能用成員函數重載,不能用友元函數重載。
1.重載下標算符 [] []是二元運算符,用於訪問數據對象的元素。重載函數調用的一般形式爲:
對象 [表達式]
2.重載函數調用符 ()
函數調用操作符() 可以看作一個二元運算符。重載函數調用的一般形式爲:
對象 ( 表達式表 )
【例6-6】定義一個向量類,用重載[]算符函數訪問向量元素,重載()算符函數返回向量長度。
6.3.4 重載流插入和流提取運算符
運算符"<<" 和">>"在C++的流類庫中重載爲插入和提取操作。程序員重載這兩個算符,通常用於傳輸用戶自定義類型的數據。這兩個算符重載函數必須爲非成員函數。
【例6-7】爲vector類重載流插入運算符和提取運算符,用於輸出和輸入數據元素。
|
|
|
|
C++中,類是用戶自定義的類型,類之間,類與基本類型之間可以像系統預定義基本類型一樣進行類型轉換。實現這種轉換使用構造函數和類型轉換函數。
6.4.1 構造函數進行類類型轉換
具有一個非默認參數的構造函數實現一種從參數類型到該類類型的轉換。構造函數的形式爲:
ClassX :: ClassX ( arg ,arg1 = E1 ,…, argn = En ) ;
其中
ClassX 用戶定義的類類型名;
arg 基本類型或類類型參數,是將被轉換成ClassX類的參數;
arg1~argn 默認參數;
E1~En 默認參數的默認值。
6.4.2 類型轉換函數
具有一個非默認參數的構造函數能夠把某種類型對象轉換成指定類對象,但不能將一個類對象轉換爲基本類型數據。爲此,C++引入一種特殊的成員函數--類型轉換函數。
類型轉換函數的形式爲 :
ClassX :: operator Type ()
{
…
return Type_Value ;
}
其中
ClassX 是類類型標識符;
Type 是類型標識符。可以是基本類型或類類型;
Type_Value 是Type類型的表達式。
這個函數的功能是把ClassX類型的對象轉換成Type類型的對象。函數沒有參數,沒有返回類型,但必須有一個返回Type類型值的語句。
類型轉換函數只能定義爲一個類的成員函數,不能定義爲類的友員。
【例6-8】有理數計算。
|
|
|
|
2.代碼實例
參考上一章代碼
第七章 繼承
1.簡介
·繼承是面向對象程序設計實現軟件重用的重要方法。程序員可以在已有類的基礎上定義新的數據成員和成員函數。原有類稱爲基類,新的類稱爲派生類,這種程序設計方法稱爲繼承。一個操作的特殊實現,用繼承方法增加新的類,不會影響原有的類層次結構。派生類成員由基類成員和自身定義的成員組成。
·單繼承的派生類只有一個基類。多繼承的派生類有多個基類。
·類成員的訪問特性和類的繼承性質決定類成員的作用域和可見性。類的公有成員稱爲接口,可以在類外訪問。派生類不能訪問基類的私有(private)成員,但可以訪問基類的公有(public)和保護(protected)成員。對基類成員的訪問性質還受繼承方式影響。公有(public)繼承方式,基類的public和protected成員在派生類中性質不變;保護(protected)繼承,基類的public和protected成員都成爲派生類的protected成員;私有(private)繼承,基類的public和protected成員都成爲派生類的private成員。
派生類中不可見基類的私有數據成員,但這些數據存儲單元依然被建立。創建派生類對象時,派生類的構造函數總是先調用基類構造函數來初始化派生類中的基類成員。調用基類構造函數可以通過初始化列表實現數據成員的初始化。調用析構函數的次序和調用構造函數的次序相反。
·類繼承關係中,覆蓋成員出現訪問的二義性,可以用作用域符顯示指定類成員。
·爲了避免多繼承類格中的匯點類在派生類對象中產生不同副本,C++提供虛繼承機制。多繼承提供了軟件重用的強大功能,也增加了程序的複雜性。 |
|
C++中,描述類繼承關係的語法形式是:
class 派生類名 : 基類名錶
{
數據成員和成員函數說明
};
其中"基類名錶"由以下語法形式構成:
訪問控制 基類名1, 訪問控制 基類名2 ,… , 訪問控制 基類名n
"訪問控制"是表示繼承權限的關鍵字,稱爲訪問描述符:
public 公有繼承
private 私有繼承
protected 保護繼承
|
|
|
|
派生類的構造函數使用冒號語法的初始化式,用指定的參數傳遞給基類的構造函數。
構造函數名(變元表):基類(變元表),數據成員1(變元表),…,數據成員(變元表)
{ /* …… */ }
構造函數的執行順序是:首先基類;然後類對象成員;最後派生類。
|
|
|
|
2.代碼實例
//銷售人員的繼承關係
#include "pch.h"
#include <iostream>
#include <string>
using namespace std;
class Employee
{public:
Employee()
:basicSalary(2000)
{
}
Employee(int mNum, string mName)
:basicSalary(2000)
{
number = mNum;
}
double pay()
{
totalSalary = basicSalary;
return totalSalary;
}
void print()
{
cout << number << '\t' << name << '\t' << totalSalary << endl;
}
friend istream& operator>>(istream&input, Employee &A);
protected:
int number;
string name;
double basicSalary;
double totalSalary;
};
class Salesman:public Employee
{
public:
Salesman()
:commrate(5.0/1000),sales(0)
{
}
Salesman(int mNum,string mName,double mSales)
:commrate(5.0 / 1000), sales(0)
{
sales = mSales;
}
double pay()
{
totalSalary = basicSalary+sales*commrate;
return totalSalary;
}
void print()
{
cout << number << '\t' << name << '\t' << totalSalary << endl;
}
double Getsales()
{
return sales;
}
friend istream& operator>>(istream&input, Salesman &A);
protected:
double sales;
double commrate;
};
class Salesmanager:public Salesman
{
public:
Salesmanager()
{
basicSalary = 2000;
jobSalary = 3000;
}
Salesmanager(int mNum, string mName, double mSales)
{
basicSalary = 2000;
jobSalary = 3000;
sales = mSales;
}
double pay()
{
totalSalary = basicSalary + jobSalary+sales * commrate;
return totalSalary;
}
void print()
{
cout << number << '\t' << name << '\t' << totalSalary << endl;
}
friend istream& operator>>(istream&input, Salesmanager &A);
protected:
double jobSalary;
};
istream& operator>>(istream&input, Employee &A)
{
input >> A.number >> A.name;
A.pay();
return input;
}
istream& operator>>(istream&input, Salesman &A)
{
input >> A.number >> A.name>>A.sales;
A.pay();
return input;
}
istream& operator>>(istream&input, Salesmanager &A)
{
input >> A.number >> A.name >> A.sales;
A.pay();
return input;
}
int main()
{
cout << "輸入普通員工信息(工號、姓名、銷售額)\n";
Employee A;
cin >> A;
A.print();
cout << "輸入銷售員信息(工號、姓名、銷售額)\n";
Salesman B;
cin >> B;
B.print();
cout << "sales=" << B.Getsales() << endl;
cout << "輸入銷售經理信息(工號、姓名、銷售額)\n";
Salesmanager C;
cin >> C;
C.print();
}
第八章 虛函數與多態性
1.簡介
·虛函數和多態性使軟件設計易於擴充。
·冠以關鍵字virtual的成員函數稱爲虛函數。派生類可以重載基類的虛函數,只要接口相同,函數的虛特性不變。
·如果通過對象名和點運算符方式調用虛函數,則調用關聯在編譯時由引用對象的類型確定,稱爲靜態聯編。
·基類指針可以指向派生類對象;以及基類中擁有虛函數,是支持多態性的前提。當通過基類指針或引用使用虛函數時,C++在程序運行時根據所指對象類型在類層次中正確選擇重定義的函數。這種運行時的晚期匹配稱爲動態聯編。
·如果一個基類中包含虛函數,通常把它的析構函數說明爲虛析構函數。這樣,它的所有派生類析構函數也自動成爲虛析構函數(即使它們與基類析構函數名字不相同)。虛析構函數使得用delete算符刪除對象時,系統可以正確地調用析構函數。
·純虛函數是在說明時代碼“初始化值”爲0的虛函數。純虛函數本身沒有實現,由它的派生類定義實現版本。
·具有純虛函數的類稱爲抽象類。抽象類只能作爲基類,不能建立實例化對象。如果抽象類的派生類不提供純虛函數的實現,則它依然是抽象類。定義了純虛函數實現的派生類,即可以建立對象的類稱爲具體類。
·儘管不能建立抽象類對象,但抽象類機制提供的軟件抽象和可擴展性的手段;抽象類指針使得派生的具體類對象具有多態操作能力。異質鏈表是多態應用的一個實例。 |
|
基類指針和派生類指針與基類對象和派生類對象有4種可能匹配使用方式:
(1)直接用基類指針引用基類對象;
(2)直接用派生類指針引用派生類對象;
(3)用基類指針引用派生類對象;
(4)用派生類指針引用基類對象。
第(1)、(2)種情況的使用無疑是安全的。下面討論第(3)和第(4)種情況。
8.2.1 基類指針引用派生類對象
派生類是基類的變種。一般情況下,基類指針指向派生類對象時,只能引用基類成員。
【例8-1】使用基類指針引用派生類對象。
8.2.2 派生類指針引用基類對象
派生類指針只有經過強制類型轉換之後,才能引用基類對象。
【例8-2】日期時間程序。
【例8-3】重寫例8-2,在派生類中調用基類同名成員函數。
|
|
|
|
冠以關鍵字virtual的成員函數稱爲虛函數。
實現運行時多態的關鍵是先要說明虛函數,用同一個基類指針訪問虛函數,才能實現運行時的多態。
8.3.1 虛函數和基類指針
基類指針雖然獲取派生類對象地址,卻只能訪問派生類從基類繼承的成員。以下的簡單例子演示這種情況。
【例8-4】演示基類指針的移動。
【例8-5】虛函數應用。把例8-3中的成員函數who()說明爲虛函數,可以看到不同的運行效果。
定義虛函數時注意:
(1)一旦一個成員函數被說明爲虛函數,所有派生類的重載函數都保持虛特性。
(2)虛函數必須是類的成員函數。
(3)不能將友員說明爲虛函數,但虛函數可以是另一個類的友員。
(4)析構函數可以是虛函數,但構造函數不能是虛函數。
8.3.2 虛函數的重載特性
重載一個虛函數時,要求函數名、返回類型、參數個數、參數類型和順序完全相同。
【例8-6】虛函數的重載特性。
8.3.3 虛析構函數
虛析構函數用於動態分配類層次結構對象時,指引delete運算符選擇正確的析構調用。
【例8-7】普通析構函數在刪除動態派生類對象的調用情況。
如果用基類指針指向由new運算建立的派生類對象,而delete運算又顯式地作用於指向派生類對象的基類指針,那麼,不管基類指針所指對象是何種類型,也不管每個類的析構函數名是否相同,系統都只調用基類的析構函數。
解決辦法是將基類析構函數說明爲虛析構函數。這樣,使用delete運算符時系統會調用相應類的析構函數,刪除派生類對象時,同時刪除派生類對象的基類部分。
【例8-8】用虛析構函數刪除派生類動態對象。
爲基類提供一個虛析構函數,能夠使派生類對象在不同狀態下正確調用析構函數。
|
|
|
|
基類往往用於表達一些抽象的概念,僅說明一個公共的界面,而由各派生類提供各自的實現版本。這種情況下,基類的有些函數沒有定義是很正常的,但要求派生類必須重定義這些虛函數,以使派生類有意義。
一個具有純虛函數的基類稱爲抽象類。抽象類機制支持一般概念的表示,也可以用於定義接口。
8.4.1 純虛函數
純虛函數在該基類中沒有實現定義,要求所有派生類都必須定義自己的版本。
純虛函數的說明形式如下:
virtual 類型 函數名 ( 參數表 ) = 0 ;
其中函數用賦值爲0表示沒有實現定義。
【例8-9】簡單圖形類,以說明虛函數的使用。
8.4.2 抽象類
抽象類至少有一個純虛函數。定義了純虛函數實現版本的派生類稱爲具體類。
對抽象類的使用,可以說明抽象類的指針和引用。但是有以下限制:
·抽象類只能用作其他類的基類;
·抽象類不能建立對象;
·抽象類不能用作參數類型、函數返回類型或顯式類型轉換。
【例8-10】使用抽象類指針。
【例8-11】使用抽象類引用,以不同數制形式輸出正整數。
|
|
|
|
2.代碼實例
#include "pch.h"
#include <iostream>
using namespace std;
const double pi = 3.1415926;
class Circle
{
public:
virtual double area()=0; //虛函數
virtual double volume()=0; //虛函數
protected:
double radius;
};
double Circle::area()
{
double S;
S = pi * radius*radius;
return S;
}
class Sphere:public Circle //球體
{
public:
Sphere(double r);
virtual double area();
virtual double volume();
};
Sphere::Sphere(double r)
{
radius = r;
}
double Sphere::area()
{
double S;
S = 4 * Circle::area();
return S;
}
double Sphere::volume()
{
double V;
V = 4.0 / 3.0*pi*radius*radius*radius;
return V;
}
class Column :public Circle //圓柱體
{
public:
Column(double r,double h);
virtual double area();
virtual double volume();
private:
double height;
};
Column::Column(double r, double h)
{
radius = r;
height = h;
}
double Column::area()
{
double S;
S = 2 * Circle::area()+2*pi*radius*height;
return S;
}
double Column::volume()
{
double V;
V = Circle::area()*height;
return V;
}
int main()
{
int count = 0;
Circle *p,*q;
p = new Sphere(3.2);
q = new Column(3.3, 5.2);
cout << "球體p:\n 面積:"<<p->area();
cout << "\t體積:"<< p->volume();
cout << endl;
cout << "圓柱體q:\n 面積:"<< q->area();
cout << "\t體積:"<< q->volume();
}
第九章 模板
1.簡介
·模板是C++類型參數化的多態工具。所謂類型參數化,是指一段程序可以處理在一定範圍內各種類型的數據對象,這些數據對象呈現相同的邏輯結構。由於C++程序的主要構件是函數和類,所以,C++提供了兩種模板:函數模板和類模板。
·所有模板定義都以模板說明開始。模板說明以關鍵字template開始,以尖括號相括說明類屬參數。每個類屬參數之前冠以關鍵字typename或class。類屬參數必須在模板定義中至少出現一次。同一個類屬參數可以用於多個模板。類屬參數可以用於指定函數的參數類型、函數返回類型和說明函數中的變量。
·模板由編譯器通過使用時的實際數據類型實例化,生成可執行代碼。實例化的函數模板稱爲模板函數;實例化的類模板稱爲模板類。
·函數模板可以用多種方式重載。可以用不同類屬參數重載函數模板,也可以用普通參數重載爲一般函數。
·類模板可以從模板類派生;類模板可以從非模板類派生;模板類可以從類模板派生;非模板類可以從類模板派生。
·函數模板和類模板可以聲明爲非模板類的友員。使用類模板,可以聲明各種各樣的友員關係。
·類模板可以聲明static數據成員。實例化的模板類的每個對象共享一個模板類的static數據成員。 |
|
2.代碼實例
//1.求最大值
#include "pch.h"
#include <iostream>
#include <string>
using namespace std;
template <typename T>
class find_max
{
public:
find_max(T ma, T mb, T mc);
T max();
void print();
private:
T a, b, c;
};
template<typename T>
find_max<T>::find_max(T ma, T mb, T mc)
{
a = ma, b = mb, c = mc;
}
template<typename T>
T find_max<T>::max()
{
T d=(a > b ? a : b);
return c > d ? c : d;
}
template<typename T>
void find_max<T>::print()
{
cout << a <<" "<< b<<" " << c<<endl;
}
int main()
{
find_max<int>A(3, 6, 1);
A.print();
cout<<"三個整數的最大值爲:"<<A.max()<<endl;
find_max<double>B(3.4, 6.3, 1.7);
B.print();
cout << "三個小數的最大值爲:" << B.max()<<endl;
find_max<string>C("Guangzhou", "Beijing", "Zhongguo");
C.print();
cout << "三個字符串的最大值爲:"<<C.max()<<endl;
}
//2、數組計算
#include <iostream>
#include <string>
using namespace std;
template<typename T>
class Array
{
public:
Array(T *mp, int msize);
Array(int msize);
~Array();
void input();
T sum();
T average();
void print();
private:
int size;
T *p;
};
template<typename T>
Array<T>::Array(T *mp, int msize)
{
size = msize;
p = new T[msize];
for (int i = 0; i < msize; ++i)
{
p[i] = mp[i];
}
}
template<typename T>
Array<T>::Array(int msize)
{
size = msize;
}
template<typename T>
Array<T>::~Array()
{
delete[]p;
}
template<typename T>
void Array<T>::input()
{
p = new T[size];
cout << "請輸入" << size << "個數據:";
for (int i = 0; i < size; ++i)
{
cin >> p[i];
}
}
template<typename T>
T Array<T>::sum()
{
T s = 0;
for (int i = 0; i < size; ++i)
{
s += p[i];
}
return s;
}
template<typename T>
T Array<T>::average()
{
T s = sum();
return s / size;
}
template<typename T>
void Array<T>::print()
{
cout << "數組數據如下:\n";
for (int i = 0; i < size; ++i)
{
cout << p[i] << ' ';
if ((i + 1) % 10 == 0) cout << '\n';
}
}
int main()
{
char op;
while (true)
{
cout << "選擇默認初始化or自行添加數據(1 or 2): ";
cin >> op;
if (op == '1')
{
int p[] = { 1,2,3,4,5,6,7,8,9,10 };
Array<int>A(p, sizeof(p) / sizeof(int));
cout << "總和=" << A.sum() << endl;
cout << "平均值=" << A.average() << endl;
A.print();
double q[] = { 1.5,2.4,3.3,4.2,5.1,6.0,7.6,8.7,9.8,10.9 };
Array<double>B(q, sizeof(q) / sizeof(double));
cout << "總和=" << B.sum() << endl;
cout << "平均值=" << B.average() << endl;
B.print();
}
else if (op == '2')
{
cout << "請輸入要添加的數據類型(int or double):";
string type;
cin >> type;
if (type == "int")
{
int msize;
cout << "請輸入數組元素個數:";
cin >> msize;
Array<int>C(msize);
C.input();
cout << "總和=" << C.sum() << endl;
cout << "平均值=" << C.average() << endl;
C.print();
cout << endl;
}
if (type == "double")
{
int msize;
cout << "請輸入數組元素個數:";
cin >> msize;
Array<double>D(msize);
D.input();
cout << "總和=" << D.sum() << endl;
cout << "平均值=" << D.average() << endl;
D.print();
cout << endl;
}
}
else
{
cout << "輸入有誤!\n";
continue;
}
}
}
第十章 輸入/輸出流
1.簡介
·C++流庫是用繼承方法建立起來的一個輸入輸出類庫。流是對字節序列從一個對象移動到另一個對象的抽象。
·流對象是內存與文件(或字符串)之間數據傳輸的信道。標準流與系統預定義的外部設備連接;串流與串對象或字符數組連接;文件流與用戶定義的外部文件連接。一旦流對象和通信對象關聯,程序就可以使用流的操作方式傳輸數據。
·標準流與外設的連接是C++預定義的。其他流類對象與通信對象的連接使用流類構造函數實現。
·數據流本身沒有邏輯格式。數據的解釋方式由應用程序的操作決定。流類庫提供了格式化和非格式化的I/O功能。
·文本流I/O提供內存基本類型數據與文本之間的格式轉換。包括重載插入和提取運算符,字符輸入輸出函數,以及各種格式控制。標準流、串流都是文本流。
·處理用戶定義的文件I/O要用文件流對象。根據代碼方式分爲文本文件和二進制文件,根據數據存取方式分爲順序存取文件和隨機存取文件。文本文件是順序存取文件,二進制文件是隨機存取文件。
·文件操作的三個主要步驟是:打開文件;讀/寫文件;關閉文件流。
·文件的性質由打開文件的方式決定。文本文件流提供數據格式化I/O;二進制文件流提供字節流無格式I/O。移動流指針,可以對文件的任意位置進行讀/寫操作。 |
|
10.1.1 流類庫
C++的流庫(stream library)是用繼承方法建立起來的一個輸入輸出類庫,它主要有兩個平行的基類:streambuf類和ios類。它們是所有流類的基類。還有一個獨立的類iostremm_init用於流類的初始化操作。
圖 10.1 streambuf類及其派生類
streambuf主要負責緩衝區的處理,類結構見圖10.1。
圖 10.2 ios的類層次結構
ios類層次提供流的高級I/O操作。類層次見圖10.2所示。
10.1.2 頭文件
C++的iostream類庫提供了數百種I/O功能,iostream類庫的接口部分包含在幾個頭文件中。例如,常用以下幾個頭文件:
iostream.h 包含了操作所有輸入/輸出流所需的基本信息。該文件含有cin(標準輸入流)、cout(標準輸出流)、cerr(非 緩衝錯誤輸出流)、clog(緩衝錯誤輸出流)四個對象,提供了無格式和格式化的I/O功能。
iomanip.h 包含格式化I/O的帶參數操縱算子。這些算子用於指定數據輸入輸出的格式。
fstream.h 包含處理文件的有關信息,提供建立文件,讀/寫文件的各種操作接口。
|
|
|
標準流是C++預定義的對象,主要提供內存外部設備進行數據交互的功能,包括數據提取、插入、解釋以及格式化處理等。流的操作是流類的公有成員函數。
10.2.1 標準流
標準流對象(常簡稱爲標準流)是爲用戶常用外部設備提供的與內存之間通信的通道。它們對數據進行解釋和傳輸,提供必要數據緩衝等。標準流與外部設備之間的關係見圖10-3所示。
圖10.3 標準流
(1)cin
istream類的對象,標準輸入流。通常連向鍵盤,可以重定向。輸入流的提取運算符可以識別輸入字節序列類型的數據。
(2)cout
ostream類的對象,標準輸出流。通常連向顯示器,可以重定向。輸出流的插入運算把內存的基本類型數據轉換成相應的字符序列,輸出到標準輸出設備上。
(3)cerr
ostream類的對象,標準錯誤輸出流,連向顯示器。
(4)clog
ostream類的對象,標準錯誤輸出流,連向打印機。不能重定向。
標準流連接的外部設備都是文本形式的設備,標準流的主要工作是內存基本類型數據與文本之間的翻譯和傳輸。能夠處理基本類型數據與文本之間I/O的流類稱爲文本流。
|
|
|
|
10.2.2 輸入流操作
流操作主要是提取和插入數據。輸入操作指輸入流對象從流中提取數據,寫入內存。最常用的方法是使用流提取運算符" >>"輸入基本類型數據。
提取運算符具有類型轉換功能,將輸入流中的空白、Tab鍵、換行符等特殊字符作爲分隔符。表10-1列出了istream類的一些公有成員函數,它們以不同的方法提取輸入流中的數據。
函數
|
功能
|
read
|
無格式輸入指定字節數
|
get
|
從流中提取字符,包括空格
|
getline
|
從流中提取一行字符
|
ignore
|
提取並丟棄流中指定字符
|
peek
|
返回流中下一個字符,但不從流中刪除
|
gcount
|
統計最後輸入的字符個數
|
eatwhite
|
忽略前導空格
|
seekg
|
移動輸入流指針
|
tellg
|
返回輸入流中指定位置的指針值
|
operator>>
|
提取運算符
|
表10-1中的函數,C++會提供不同的重載版本。
【例10-1】用get函數從鍵盤輸入字符。
10.2.3 輸出流操作
輸出操作是把內存的數據插入流中。常用的方法是使用流插入運算符"<<",輸出C++基本類型的數據項。
表10-2列出了ostream提供的主要公有成員函數。
函數
|
功能
|
put
|
無格式 , 插入一個字節
|
write
|
無格式 , 插入一字節序列
|
plush
|
刷新輸出流
|
seekp
|
移動輸出流指針
|
tellp
|
返回輸出流中指定位置的指針值
|
operator<<
|
插入運算符
|
10.2.4 流錯誤狀態
ios類中,定義了一個記錄流錯誤狀態的數據成員,稱爲狀態字。錯誤狀態字描述見表10-3。
標識常量
|
值
|
意義
|
goodbit
|
0x00
|
狀態正常
|
eofbit
|
0x01
|
文件結束符
|
failbit |
0x02 |
I/O 操作失敗,數據未丟失,可以恢復 |
badbit
|
0x04
|
非法操作,數據丟失,不可恢復
|
ios有幾個與流錯誤狀態有關的公有成員函數。
int eof() const;
返回eofbit狀態值。當遇到文本文件結束符時,輸入流中自動設置eofbit。
int fail() const;
返回failbit狀態值,判斷流操作是否失敗。failbit發生流格式錯誤,字符沒有丟失。這種錯誤通常是可以修復的。
int good() const;
int operator void *();
上述兩個函數,如果bad、fail和eof全部返回false,即eofbit、failbit和badbit都沒有被設置,則返回1(true),否則返回0(false)。
int bad() const;
int operator !();
上述兩個函數,只要eofbit、failbit或badbit其中一個被設置,則返回1(true),否則返回0(false)。
int rdstate() const;
返回狀態字。例如,函數調用
cout.rdstate()
將返回流的當前狀態。隨後可以用位測試的方法檢查各種錯誤狀態。
void clear( int nState = 0 );
恢復或設置狀態字。默認參數爲0,即ios::goodbit,對狀態字清0,把流狀態恢復爲正常。例如:
cin.clear();
清除cin,併爲流設置goodbit。
cin.clear(ios::failbit) ;
給流設置了failbit。有時程序操作遇到某些問題時可能需要這樣做,當問題解決後再恢復。
|
|
|
|
ios提供直接設置標誌字的控制格式函數,iostream和iomanip庫還提供了一批控制符簡化I/O格式化操作。
10.3.1 設置標誌字
1.標誌常量
ios類中說明了一個數據成員,用於記錄當前流的格式化狀態,稱爲標誌字。標誌字的每一位用於記錄一種格式。爲便於記憶,每一種格式定義了對應的枚舉常量。程序中,可以使用標誌常量或直接用對應的十六進制值設置輸入輸出流的格式。表10-4列出主要標誌常量名及其意義。
標誌常量
|
值
|
意義
|
輸入 / 輸出
|
ios::skipws
|
0X0001
|
跳過輸入中的空白
|
I
|
ios::left
|
0X0002
|
按輸出域左對齊輸出
|
O
|
ios::right
|
0X0004
|
按輸出域右對齊輸出
|
O
|
ios::internal
|
0X0008
|
在符號位或基指示符後填入字符
|
O
|
ios::dec
|
0X0010
|
轉換基製爲十進制
|
I/O
|
ios::oct
|
0X0020
|
轉換基製爲八進制
|
I/O
|
ios::hex
|
0X0040
|
轉換基製爲十六進制
|
I/O
|
ios::showbase
|
0X0080
|
在輸出中顯示基指示符
|
O
|
ios::showpoint
|
0X0100
|
輸出時顯示小數點
|
O
|
ios::uppercase
|
0X0200
|
十六進制輸出時一律用大寫字母
|
O
|
ios::showpos
|
0X0400
|
正整數前加“ + ”號
|
O
|
ios::scientific
|
0X0800
|
科學示數法顯示浮點數
|
O
|
ios::fixed
|
0X1000
|
定點形式顯示浮點數
|
O
|
ios::unitbuf
|
0X2000
|
插入操作後立即刷新流
|
O
|
ios::stdio
|
0X4000
|
插入操作後刷新 stdout 和 stdree
|
O
|
2.ios中控制格式的函數
ios有幾個直接操作標誌字的公有成員函數:
(1)設置和返回標誌字
long flags( long lFlags );
用參數lFlags更新標誌字,返回更新前的標誌字。
long flags() const;
返回標誌字。
(2)操作標誌字
long setf( long lFlags );
設置參數lFlags指定的標誌位,返回更新前的標誌字。
long setf( long lFlags, long lMask );
將參數lMask指定的標誌位清0,然後設置參數lFlags指定的標誌位,返回更新前的標誌字。
(3)清除標誌字
long unsetf( long lFlags );
將參數lFlags指定的標誌位清0,返回更新前的標誌字。
ios還有幾個設置輸出數據寬度、填充字符和置輸出顯示精度的函數:
(4)設置和返回輸出寬度
int width( int nw );
設置下一個輸出項的顯示寬度爲nw。如果nw大於數據所需寬度,在沒有特別指示時,數據以右對齊方式顯示。如果nw小於數據所需寬度,則nw無效,數據以默認格式輸出。該函數的設置沒有持續性,輸出一個項目之後,恢復系統的默認設置。
int width() const;
返回當前的輸出寬度值。
(5)設置填充字符
char fill( char cFill );
當設置寬度大於數據顯示需要寬度時,空白位置以字符參數cFill填充。若數據在寬度域左對齊,在數據右邊填充,否則在左邊填充。默認填充符爲空白符。
char fill() const;
返回當前使用的填充符。
(6)設置顯示精度
int precision( int np );
用參數np設置數據顯示精度。如果浮點數以定點形式輸出,np表示有效位數。如果浮點數設置爲科學示數法輸出,則np表示小數點後的位數。
系統提供顯示精度的默認值是6。float類型最大設置示數精度爲6位,double類型最大設置示數精度爲15位。
int precision() const;
返回當前示數精度值。
【例10-2】設置輸出寬度。
【例10-3】不同基數形式的輸入輸出。
【例10-4】格式化輸出浮點數。
10.3.2 格式控制符
C++在ios的派生類istream和ostream中定義了一批函數,作爲重載插入運算符<<或提取運算符>>的右操作數控制I/O格式,稱爲控制符(或操作算子),簡化了格式化輸入輸出代碼編寫。這些控制符在iostream.h或iomanip.h文件中聲明。 1.iostream中的控制符
iostream.h幾個常用的控制符見表10-5。
控制符
|
功能
|
輸入 / 輸出
|
Endl
|
輸出一個新行符,並清空流
|
O
|
Ends
|
輸出一個空格符,並清空流
|
0
|
Flush
|
清空流緩衝區
|
0
|
Dec
|
用十進制表示法輸入或輸出數值
|
I/O
|
Hex
|
用十六進制表示法輸入或輸出數值
|
I/O
|
Oct
|
用八進制表示法輸入或輸出數值
|
I/O
|
Ws
|
提取空白字符
|
I
|
【例10-5】不同基數形式的輸入輸出。
2.iomanip中的控制符
iomanip文件在namespace std中定義中定義了若干控制符,用於設置ios的標誌字。見表10-6。
控制符
|
功能
|
輸入 / 輸出
|
resetiosflags ( ios:: lFlags )
|
清除 lFlags 指定的標誌位
|
I/O
|
setiosflags ( ios:: lFlags )
|
設置 lFlags 指定的標誌位
|
I/O
|
setbase ( int base )
|
設置基數, base = 8 , 10 , 16
|
I/O
|
setfill ( char c )
|
設置填充符 c
|
O
|
setprecision ( int n )
|
設置浮點數輸出精度
|
O
|
setw ( int n )
|
設置輸出寬度
|
O
|
表10-6中lFlags參數定義見表10-4,格式控制常量。注意,setiosflags不能代替setbase的作用。
【例10-6】整數的格式化輸出。
【例10-7】格式化輸出浮點數。
|
|
|
|
串流提取數據時對字符串按變量類型解釋;插入數據時把類型數據轉換成字符串。串流I/O具有格式化功能。在程序設計中,串流通常利用string對象或字符串作爲與外部設備交換數據的緩衝區。
串流類是 ios 中的派生類。strstream,istrstream,ostrstream類庫中定義的istrstream類和ostrstream類支持從C的字符串輸入/輸出。使用這些串流類要包含strstream頭文件。
stringstream,istringstream,ostringstream類庫中定義的istringstream類和ostringstream類支持從string對象輸入/輸出,並且更加安全。使用這些串流類要包含sstream頭文件。
【例10-8】用輸入串流對象從string對象提取數據。
【例10-9】用輸出串流對象向string對象插入數據。
|
|
|
|
C++把文件看成無結構的字節流,根據文件數據的編碼方式分爲文本文件和二進制文件。根據存取方式分爲順序存取文件和隨機存取文件。
流庫的ifstream、ofstream和fstream類用於內存與文件之間的數據傳輸。
10.5.1 文件和流
C++文件是順序排列的字節序列,長度爲n的文件,字節號從0~n-1。見圖10.4所示。
圖 10.4 C++文件
可以用ASCII碼解釋的文件稱爲文本文件。文本文件是一種解釋方式最簡單的文件。
文本文件除了可以表示可見字符外,還可以表示一些控制設備動作的特殊符號。詳細請參閱ASCII字符集。
對於文本文件,iostream定義了一個表示文件結束的標識常量EOF (End Of File),它是值爲0x1A的字符。關閉文件流時,該字符被自動添加到文件尾部。鍵盤上,可以同時按下Ctrl鍵和Z鍵,在標準輸入流cin中輸入文件結束符。
C++把非文本文件都稱爲二進制文件。二進制文件不能方便地識別文件結束符,讀寫文件時要根據流的長度或流指針的位置判斷是否已到達流的結束。
順序存取文件指對文件的元素順序操作。
隨機存取文件通過文件指針在文件內移動,可以查找到指定位置進行讀寫。
進行文件的讀寫,首先必須建立一個文件流對象,然後把這個流對象與實際文件相關聯(稱爲打開文件)一個文件用完後,需要關閉文件。
C++有三種文件流:ifstream.h頭文件包含文件輸入流類ifstream;ofstream.h頭文件包含文件輸出流類ofstream;fstream.h頭文件包含文件輸入/輸出流類fstream。
10.5.2 打開和關閉文件
文件操作總是包含三個基本步驟:
打開文件——讀/寫文件——關閉文件
1. 打開文件
打開文件操作包括建立文件流對象;與外部文件關聯;指定文件的打開方式。打開文件有兩種方法:
(1)首先建立流對象,然後調用open函數連接外部文件。
流類 對象名 ;
對象名.open( 文件名 , 方式 ) ;
(2)調用流類帶參數的構造函數,建立流對象的同時連接外部文件。
流類 對象名 ( 文件名 , 方式 ) ;
其中:
"流類"是C++流類庫定義的文件流類,爲ifstream、ofstream或fstream。
"對象名"是用戶定義標識符,流對象名。
"文件名"是用字符串表示外部文件的名字。要求使用文件全名。
"方式"是ios定義的標識常量(見表10-7),表示文件的打開方式。
例如,用第一種方式打開文件。
打開一個已有文件datafile.dat,準備讀:
ifstream infile ; //建立輸入文件流對象
infile.open( "datafile.dat" , ios::in ) ; //連接文件,指定打開方式
打開(創建)一個文件newfile.dat,準備寫:
ofstream outfile ; //建立輸出文件流對象
outfile.open( "d:\\newfile.dat" , ios::out ) ; //連接文件,指定打開方式
第二種方法是調用fstream帶參數構造函數,在建立流對象的同時,用參數形式連接外部文件和指定打開方式。
例如
ifstream infile ( "datafile.dat" , ios::in ) ;
ofstream outfile ( "d:\\newfile.dat" , ios::out );
fstream rwfile ( "myfile.dat" , ios::in | ios::out ) ;
文件打開方式見表10-7。
標識常量
|
值
|
意義
|
ios::in
|
0x0001
|
讀方式打開文件
|
ios::out
|
0x0002
|
寫方式打開文件
|
ios::ate
|
0x0004
|
打開文件時,文件指針指向文件末尾
|
ios::app
|
0x0008
|
追加方式,向文件輸出的內容追加到文件尾部
|
ios::trunc
|
0x0010
|
刪除文件現有內容( ios::out 的默認操作)
|
ios::nocreate
|
0x0020
|
如果文件不存在,則打開操作失敗
|
ios::noreplace
|
0x0040
|
如果文件存在,則打開操作失敗
|
ios::binary
|
0x0080
|
二進制方式打開,默認爲文本方式
|
2.關閉文件
關閉文件操作包括把緩衝區數據完整地寫入文件,添加文件結束標誌,切斷流對象和外部文件的連接。
關閉文件使用fstream的成員函數close。
例如:
ifstream infile ;
infile.open( "file1.txt" , ios::in ) ;
//讀文件
infile.close() ; //關閉file1.txt
infile.open("file2.txt" , ios::in ) ; //重用流對象
用close關閉文件後,若流對象的生存期沒有結束,即流對象依然存在,還可以與其他文件連接。上述語句關閉file1.txt後,重用流infile打開文件file2.txt。
當一個流對象的生存期結束,系統也會自動關閉文件。
10.5.3 文本文件
文本文件是順序存取文件,用默認方式打開,用文本文件流進行讀/寫操作。
文本文件本身是沒有什麼記錄邏輯結構。爲了便於識別,文本文件中通常用換行符分隔邏輯行記錄,用空白符、換行符、製表符等分隔數據項。
【例10-10】建立一個包含學生學號、姓名、成績的文本文件。
【例10-11】讀文本文件。
【例10-12】瀏覽文件。
【例10-13】向文件追加記錄。
【例10-14】複製文本文件。
10.5.4 二進制文件
二進制文件以基本類型數據在內存的二進制表示形式存放數據。寫操作時,從內存的指定位置開始的若干個字節插入到流中;讀操作時,從流的指定位置開始送若干字節賦給指定對象。數據的解釋由內存對象的類型決定。因此,二進制文件又稱爲類型文件。
二進制文件的讀寫方式完全由程序控制。
打開二進制文件用binary方式。從二進制文件流讀取數據用istream的read函數;向二進制文件流寫入數據用 ostream的write函數。
二進制文件是隨機存取文件。C++的流指針在字節流中移動。指針所指向的位置,對數據既可讀又可寫。
文件打開以後,系統自動生成兩個隱含的流指針--讀指針和寫指針。istream、ostream的成員函數可以返回指針的值(指向),或移動指針。用追加方式(ate, app)打開文件時,流指針指向文件末尾;用其他方式打開時,流指針指向文件的第一個字節。文件的讀/寫從流指針所指的位置開始,每完成一次讀/寫操作後,流指針自動移動到下一個讀/寫分量的起始位置。
除了讀/寫操作自動移動流指針外,istream和ostream分別提供對讀指針和寫指針的操作函數。
istream類操作讀指針的函數:
(1)移動讀指針
istream& seekg( long pos );
istream& seekg( long off, ios::seek_dir dir );
參數說明:
pos 指針的新位置值
off 指針偏移量
dir 參照位置
帶一個參數的seekg函數,直接由參數pos指定指針的新位置。
帶兩個參數的seekg函數以dir爲參照位置移動指針,偏移量爲off。seek_dir是一個枚舉類型,在ios定義爲:
enum seek_dir { beg = 0, cur = 1, end = 2 } ;
seek_dir的常量值有以下含義(見圖10.5所示):
ios::cur 當前流指針位置
ios::beg 流的開始位置
ios::end 流的尾部
圖 10.5 流指針位置
(2)返回讀指針當前指向的位置值
long tellg();
返回值是表示存儲地址的長整型值。
ostream類操作寫指針的函數:
(1)移動寫讀指針
ostream& seekp( long pos );
ostream& seekp( long off, ios::seek_dir dir );
(2)返回寫指針當前指向的位置值
long tellp();
函數參數意義與讀指針操作函數相同。
【例10-15】簡單事務處理。本程序模擬一個書店的銷售賬目。程序能夠添加、修改書目,根據進貨和銷售數目更新庫存數。
|
|
|
|
2.代碼實例
// P408Ex1.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
//以表格的形式輸出:當x=1°,2°,……,10°時sinx,cosx和tanx的值。要求:輸出時,數據的寬度爲10,左對齊,保留小數點後5位。
#include "pch.h"
#include <iostream>
#include <iomanip>
using namespace std;
const double pi = 3.1415926;
int main()
{
cout << setw(10) << setiosflags(ios::left) << "度數(°)" << setw(10) << setiosflags(ios::left) << "sin" << setw(10) << setiosflags(ios::left) << "cos" << setw(10) << setiosflags(ios::left) << "tan" << endl;
for (int i = 1; i <= 10; ++i)
{
cout << setw(10)<< i;
cout << setw(10) << setprecision(5) << fixed << sin(i*pi / 180.0) << setiosflags(ios::left); //計算sin,注意弧度制與角度制轉化
cout << setw(10) << setprecision(5) << fixed << cos(i*pi / 180.0) << setiosflags(ios::left);
cout << setw(10) << setprecision(5) << fixed << tan(i*pi / 180.0) << setiosflags(ios::left);
cout << endl;
}
}
//讀出一個作業.cpp文件,刪除全部註釋內容,即以"/*...*/"相括法文本和以"//"開始到行末的文本,生成一個新的.cpp文件。
#include "pch.h"
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int main()
{
ifstream file("test.cpp", ios::in);
ofstream newfile("newtest.cpp", ios::out);
char a = '\0', b = '\0';
while (!file.eof())
{
b = file.get();
if (a == '/'&& b == '/')
{
for (;;)
{
a = b;
b = file.get();
if (a == '\n')
{
cout << '\n';
newfile << '\n';
break;
}
}
}
else if (a == '/'&&b == '*')
{
while (true)
{
a = b;
b = file.get();
if (a == '*'&&b == '/')
{
a = b;
b=file.get();
break;
}
}
}
else
{
char ch = a;
cout << ch;
newfile<<ch;
}
a = b;
}
file.close();
newfile.close();
printf("完成!\n");
}
#include "pch.h"
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
//寫一個二進制文件用於測試
//fstream file1;
//file1.open("test1.dat", ios::out | ios::binary);
//fstream file2;
//file2.open("test2.dat", ios::out | ios::binary);
//int s[] = { 1,3,6,9,14,18,23,34,45 };
//int f[] = { 2,4,5,6,13,17,19,30 };
//file1.write((char*)&s, sizeof(s));
//file2.write((char*)&f, sizeof(f));
//file1.close();
//file2.close();
//文件合併成新的文件
fstream file1("test1.dat", ios::in | ios::binary);
fstream file2("test2.dat", ios::in | ios::binary);
fstream file3("test3.dat", ios::out | ios::binary);
int a,b;
file1.read((char*)&a, sizeof(a));
file2.read((char*)&b, sizeof(b));
while (true)
{
if (file1&&file2)
{
if (a <= b)
{
file3.write((char*)&a, sizeof(int));
file1.read((char*)&a, sizeof(a));
}
else
{
file3.write((char*)&b, sizeof(int));
file2.read((char*)&b, sizeof(int));
}
}
else if (file1&&!file2)
{
while (file1)
{
file3.write((char*)&a, sizeof(int));
file1.read((char*)&a, sizeof(int));
}
}
else if (!file1&&file2)
{
while (file2)
{
file3.write((char*)&b, sizeof(int));
file2.read((char*)&b, sizeof(int));
}
}
else
{
break;
}
}
file1.close();
file2.close();
file3.close();
cout << "文件合併完成!" << endl;
//輸出顯示
cout << "顯示file1!\n";
fstream file4("test1.dat", ios::in | ios::binary);
while (file4)
{
int a;
file4.read((char*)&a, sizeof(a));
if (!file4) break; //增加這一條件,避免最後一個元素重複輸出
cout << a << ' ';
}
file4.close();
cout << endl;
cout << "讀取完成!";
cout << "顯示file2!\n";
fstream file5("test2.dat", ios::in | ios::binary);
while (file5)
{
int a;
file5.read((char*)&a, sizeof(a));
if (!file5) break;
cout << a << ' ';
}
file5.close();
cout << endl;
cout << "讀取完成!";
cout << "顯示file3!\n";
fstream file6("test3.dat", ios::in | ios::binary);
while (file6)
{
int a;
file6.read((char*)&a, sizeof(a));
if (!file6) break;
cout << a << ' ';
}
file6.close();
cout << endl;
cout << "讀取完成!";
}
第十一章 異常處理
參考:https://www.runoob.com/cplusplus/cpp-exceptions-handling.html