---恢復內容開始---linux
c++ primer plus 第6版 部分二 5- 章ios
第五章c++
計算機除了存儲外 還能夠對數據進行分析、合併、重組、抽取、修改、推斷、合成、以及其餘操做程序員
1.for循環的組成部分算法
a 設置初始值spring
b 執行測試,看循環時候應當繼續進行express
c 執行循環操做編程
d 更新用於測試的值windows
只要測試表達式爲true 循環體就會執行數組
for (initialization; test-expression; update-expression)
body
test-expression決定循環體是否被執行,一般這個表達式是關係表達式,c++會將此結果強制轉換爲bool類型。值0的表達式將被轉換爲bool值false,致使循環結束。
for 後面跟着一對括號 它是一個c++關鍵字 所以編譯器不會將for視爲一個函數
1. 表達式和語句
任何值或任何有效的值和運算符的組合都是表達式。c++中每一個表達式都有值。一般值是很明顯的。
c++將賦值表達式的值定義爲左側成員的值,所以這個表達式的值爲20.因爲賦值表達式有值,能夠編寫
maids=(cooks=4)+3; 值爲7
賦值表達式從右向左結合
x<y 這樣的關係表達式被斷定爲true或者false
int x;
cout<<(x=100)<<endl; //x=100
cout<<(x<3)<<endl; //0
cout<<(x>3)<<endl; //1
cout.setf(ios_base::boolalpha);//老式的c++實現使用ios:boolalpha 來做爲setf()的參數
cout<<(x<3)<<endl; //false
cout在現實bool值以前將他們轉換爲int 可是cout.setf(ios::boolalpha) 函數調用設置了一個標記,該標記命令cout顯示true和false 而不是1和0;
斷定表達式的值 改變了內存中數據的值時,表達式有反作用 side effect。
例如x++ 斷定賦值表達式 改變了x的值 因此有反作用
有例如x+100 斷定賦值表達式 計算出一個新值 未改變x的值 因此沒有反作用
表達式到語句的轉換很是容易 只須要加上分號便可。
2.非表達式和語句
返回語句 聲明語句 和for語句都不知足「語句=表達式+分號」這種模式
int a; 這是一條語句 可是int a並非表達式 由於它沒有值
因此這些代碼是非法的:
eggs=int a *1000;
cin>>int a;
不能把for循環賦值給變量
3.修改規則
c++ 在c循環的基礎上添加了一些特性 能夠對for循環句法作一些調整
for(expression;expression;expression)
statement;
示例:
for(int i=0;i<5;i++) int i=0 叫作生命語句表達式 (不帶分號的聲明) 此種只能出如今for語句中
其中 int i=0 爲聲明 不是表達式 這因爲上面的語法向矛盾
因此這種調整已經被取消
修改以後的語法:
for (for-init-statement condition;expression)
statement
感受奇怪,這裏只用了一個分號,可是這是容許的由於for-init=statement被視爲一條語句,而語句有本身的分號。對於for-init-statement來講,它既能夠是表達式語句,也能夠是聲明。
語句自己有本身的分號,這種句法規則用語句替換了後面跟分號的表達式。在for循環初始化部分中聲明和初始化變量。
在for-init-statement中聲明變量還有其實用的一面。這種變量只存在於for語句中,當程序離開循環後,這種變量將消失。
for(int i=0;i<5;i++)
較老的c++實現遵循之前的規則,對於前面的循環,將把i視爲在循環以前聲明 因此在循環結束後,i變量仍然存在。
4.回到for循環
const int arsize=16;// 定義整型的常量 在下方程序中使用 數組長度的定義
for(int i=2;i<arsize;i++) //i<arsize 下標從0到arsize-1 因此數組索引應該在arsize-1的位置中止。也可使用i<=arsize-1 可是沒有前面的表達式好。
5.修改步長
以前的循環計數都是加1或者減1 能夠經過修改更新表達式來修改步長。i=i+by
int by;
cin>>by;
for(int i=0;i<100;i=i+by)
注意檢測不等號一般要比檢測相等要好。上例中若是將i<100改成i==100 則不可行 由於i的取值不會爲100
using聲明和using編譯指令的區別。
6.使用for循環訪問字符串
示例:
#include <iostream>
#include <string>
int main(){
using namespace std;
cout<<"enter a word"<<endl;
string word; //定義對象
cin>>word;
for(int i=word.size()-1;i>=0;i--) //word對象的字符串長度 size成員函數
cout<<word[i]; //輸出各個字符
cout<<"\n----Bye.\n"<<endl;
return 0;
}
若是所用的實現沒有添加新的頭文件,則必須使用string.h 而不是cstring
7.遞增運算符++ 遞減運算符--
都是講操做數相加或者相減 他們有兩種不一樣的形式 一種爲前綴 另外一種爲後綴 ++x x++
可是他們對操做數的影響的時間是不一樣的
int a=20 b=20;
cout<<"a++="<<a++<<"++b= "<<++b<<endl;
cout<<"a="<<a<<"b= "<<b<<endl;
a++爲使用a的當前值計算表達式 而後將a的值加1 使用後修改
++b是先將b的值加1,而後使用新的值來計算表達式 修改後再使用
8.反作用 和順序點
side effect 反作用
指的是在計算表達式時對某些東西進行了修改 例如存儲在變量中的值
順序點是程序執行過程當中的一個點。c++中 語句中的分號就是一個順序點 這就意味着程序處理下一條語句以前,賦值運算符、遞增運算符和遞減運算符執行的全部修改都必須完成。任何完整的表達式末尾都是一個順序點。
表達式的定義:不是另一個表達式的子表達式
順序點有助於闡明後綴遞增什麼時候進行
while(guest++<10)
cout<<guests<<endl;
相似於只有測試表達式的for循環。可能會被認爲是使用值而後再遞增即cout中先使用guests的值 而後再將其值加1。
可是guests++<10 是一個完整的表達式,由於它是while循環的一個測試條件。因此該表達式的末尾是一個順序點。因此c++確保反作用(將guests+1) 在程序進入cout以前完成,然而使用後綴的格式,能夠確保將guests同10進行比較後再將其值加1。
y=(4+x++)+(6+x++);
表達式4+x++不是一個完整的表達式,所以,c++不保證x的值在計算子表達式4+x++後馬上增長1;在這個例子中 紅色部分是一個完整的表達式 其中的分號標示了順序點,所以c++只保證程序執行到下一條語句以前,x的值將被遞增兩次。c++沒有規定是在計算每一個子表達式以後將x的值遞增,仍是在整個表達式計算完畢後纔將x的值遞增,有鑑於此,應該避免使用這樣的表達式。
c++11文檔中,再也不使用術語「順序點」了,由於這個概念難以用於討論多線程執行。相反,使用了術語「順序」,它表示有些事件在其餘事件前發生。這種描述方法並不是要改變規則,而旨在更清晰第描述多線程編程。
10.前綴格式和後綴格式
顯然,若是變量被用於某些目的(如用做函數參數或給變量賦值),使用前綴格式和後綴格式的結果將是不一樣的。然而,若是遞增表達式的值沒有被使用,狀況會變得不一樣例如
x++;
++x;
for(n=lim;n>0;--n)
for(n=lim;n>0;n--)
從邏輯上說,在上述兩種情形下,使用前綴格式和後綴格式沒有任何區別。表達式的值未被使用,所以只存在反作用。
在上面的例子中使用這些運算符的表達式爲完整表達式,所以將x+1和n-1的反作用將在程序進入下一步以前完成,因此前綴與後綴格式的最終效果相同。
前綴與後綴的區別2:它們的執行速度有略微的差異 雖然對程序的行爲沒有影響
c++容許您針對類定義這些運算符,在這種狀況下,用戶這樣定義前綴函數:將值加1而後返回結果。
後綴版本:首先複製一個副本,將其加1 而後將複製的副本返回
顯然前綴的效率要比後綴版本高。
總之,對於內置類型,採用哪一種格式不會有差異;可是對於用戶定義的類型,若是有用戶定義的遞增和遞減運算符,則前綴格式的效率更高。
11.遞增/遞減運算符和指針
能夠將遞增運算符用於指針和基本變量。將遞增運算符用於指針時,將把指針的值增長其指向的數據類型佔用的字節數,這種規則適用於對指針遞增和遞減。
double arr[5]={21.1,32.8,23.4,45.2,37.4}
double * pt=arr; //pt points to arr[0],i.e. to 21.1
++pt;//指向arr[1] i.e. to 32.8
也能夠結合使用這些運算符來修改指針指向的值。將*和++同時用於指針時提出了這樣的問題:
將什麼解除引用,將什麼遞增?
取決於運算符的位置和優先級。
1.前綴遞增、後綴遞減和解除引用運算符的優先級相同,以從右到左的方式進行結合。
2.後綴遞增和後綴遞減的優先級相同,可是比前綴運算符的優先級高,這兩個運算符以從左到右的方式進行結合。
例如:*++pt的含義 先將++應用於pt 而後再將*應用於遞增後的pt pt的指向已經變化
++*pt 先取得pt指向的值,而後再將這個值加1 pt的指向沒有變化
(*pt)++ pt指針先解除引用 獲得值 而後再加1 原來爲32.8 以後的值爲32.8+1=33.8 pt的指向未變
x=*pt++; 後綴的運算符++的優先級更高,意味着將運算符用於pt 而不是*pt,所以對指針遞增。而後後綴運算符意味着將對原來的地址&arr[2],而不是遞增後的新地址解除引用,所以*pt++的值爲arr[2],
#include <iostream>
int main(){
using namespace std;
int word[10]={1,2,3,4,5,6,7,8,9,10};//定義數組
int *pt= word; //定義指針指向的數組
int x;
for(int i=0;i<10;i++)
cout<<word[i]<<" , ";
cout<<endl;
cout<<&word<<endl;//輸出數組首地址
cout<<pt<<endl; //指針的方式輸出數組首地址
for(int i=0;i<10;i++)
{
cout<<"dizhi= "<<&(word[i])<<" "; //輸出數組的每一個元素的地址
cout<<"*pt++= "<<*pt++<<" "; //輸出指針形式的每一個元素
cout<<"&pt=="<<pt<<endl; //地址的變化 關鍵是查看這兩行
}
return 0;
}
12.組合賦值運算符
i=i+by 更新循環計數
i+=by
+=運算符將兩個操做數相加,並將結果賦值給左邊的操做數。這意味着左邊的操做數必須可以被賦值,如變量、數組元素、結構成員或者經過對指針解除引用來標識的數據:
示例:
k+=3; 左邊能夠被賦值 能夠k=k+3
int * pa= new int[10]; pa指向數組int
pa[4]=12;
pa+=2; pa指向 pa[4]
34+=10; 非法 34不是一個變量 不能夠被賦值
13.複合語句 (語句塊)
for循環中的循環體 就是複合語句 由大括號括進
外部語句塊中定義的變量在內部語句塊中也定義的變量 狀況以下:在聲明位置到內部語句塊結束的範圍以內,新變量將隱藏舊變量,而後舊變量再次可見以下
#inlcude <iostream>
{ using namespace std;
int x=20; //原始的變量
{ //代碼塊開始
cout<<x<<endl; //使用原始的變量 20
int x=100; //定義新的變量
cout<<x<<endl; //使用新的變量 100
}
cout<<x<<endl; //使用舊的變量 20
return 0;
}
14.其餘語法技巧----逗號運算符
語句塊容許把兩條或更多條語句放到按c++句法只能放一條語句的地方。
逗號運算符對錶達式完成一樣的任務,容許將兩個表達式放到c++句法只容許放一個表達式的地方。
例如循環控制部分的更新部分 只容許這裏包含一個表達式,可是有i和j兩個變量,想同時變化 此時就須要逗號運算符將兩個表達式合併成一個
++j,--i 這兩個表達式合併成一個
逗號並不老是逗號運算符 例如 聲明中的逗號將變量列表中相鄰的名稱分開:
int i,j;
for(j=0,i=word.size()-1;j<i;--i,++j)
逗號運算符另外的特性:確保先進算第一個表達式,而後計算第二個表達式(逗號運算符是一個順序點)
i=20,j=2*i
另外逗號表達式的值爲上面第二部分的值 上面爲40,由於j=2*i的值爲40. 逗號的運算符的優先級是最低的。
data=17,24 被解釋爲(data=17),24 結果爲17 24不起做用
data=(17,24) 結果爲24 由於括號的優先級最高 從右向左結合 括號中的值爲24(逗號右側的表達式的值) 因此data的值最後爲24
15 關係表達式
關係運算符的優先級比算數運算符的低。 注意將bool的值提高爲int後,3>y要麼是1 或者是0 因此表達式有效
for(x=1;y!=x;++x) 有效
for(cin>>x;x==0;cin>>x)
(x+3)>(y-2) 有效
x+(3>y)-2 有效
16賦值 比較和可能犯的錯誤
== 和= 是有區別的 前者是判斷 關係表達式 後者是賦值表達式
若是用後者則當在for循環中 測試部分將是一個複製表達式 ,而不是一個關係表達式,此時雖然代碼仍然有效,可是賦值表達式 爲非0 則結果爲true 。
示例:測試數組中的分數
for(i=0;scores[i]==20;i++) 測試成績是否爲20 原來的數組值不變
for(i=0;socres[i]=20;i++) 測試成績是否爲20 可是原來的數組值已經都賦值爲20了
1.賦值表達式 爲非0 則 始終爲true 2.實際上修改的數組的元素的數據 3. 表達式一直未true 因此程序再到達數組結尾後,仍然在不斷地修改數據
可是代碼在語法上是正確的,所以編譯器不會將其視爲錯誤
對於c++類,應該設計一種保護數組類型來防止越界的錯誤。循環須要測試數組的值和索引的值。
17.c風格字符串的比較
數組名錶明數組首地址
引號括起來的字符串常量也是其地址
因此word=="mate" 並非判斷兩個字符串是否相同,而是查看它們是否存儲在相同的地址上 前者爲數組 後者爲字符串常量 ,因此兩個地址必定是不一樣的,即便word數組中的數據是mate字符串。
因爲c++將c風格的字符串視爲地址,因此使用關係運算符來比較它們,將不會獲得滿意的結果。
相反應該使用c風格字符串庫中的strcmp()函數來比較。 此函數接收兩個字符串地址做爲參數,參數可使指針、字符串常量或者字符數組名,若是兩個字符串相同,則函數返回0;若是第一個字符串的字母順序排在第二個字符串以前,則strcmp()函數將會返回一個負值,相反則返回一個正值。
還有按照「系統排列順序」比「字母順序」更加準確,此時意味着字符是針具字符的系統編碼來進行比較的ascii碼。全部的大寫字母的編碼都比小寫字母要小,因此按順序排列,大寫位於小寫以前。
另外一個方面 c字符串經過結尾的空值字符定義,而不是數組的長度
因此char big[80]="daffy" 與 char little[10]="daffy" 字符串是相同的
不能用關係運算符來比較字符串,可是能夠用來比較字符,由於字符其實是整型 因此能夠用下面的代碼顯示字母表中的字符
for(ch='a';ch<='z';ch++)
cout<<ch;
示例:
#include <iostream>
#include <cstring>
int main(){
using namespace std;
char word[5]="?ate"; //定義字符數組
for(char ch='a';strcmp(word,"mate");ch++)//判斷字符數組中的首元素是否爲m 爲循環的條件 若是不是則返回負值 非0 因此爲真,直到 ch=l時 ch++=m strcmp返回0 =false 結束循環
{
cout<<word<<endl;
word[0]=ch;//修改字符數組的首元素爲ch
}
cout<<"after loop ends,word is "<<word<<endl;
return 0;
}
檢測相等或排列順序
可使用strcmp()來測試c風格字符串是否相等 排列順序,若是str1和str2相等,則下面的表達式爲true
strcmp(s1,s2)==0 s1 s2相等 函數返回0 0==0 則爲true
strcmp(s1,s2)!=0 s1 s2相等 .. 0!=0 、爲false
strcmp(s1,s2) s1 s2相等返回0 爲false
strcmp(s1,s2)<0 s1在s2的前面爲true s1-s2<0
因此根據測試的條件 strcmp()能夠扮演== != <和>運算符的角色
char其實是整型 修改char 類型的操做其實是修改存儲在變量中的整數的編碼 另,使用數組索引可以使修改字符串中的字符更爲簡單
18.比較string類字符串
使用string類的字符串 比使用c風格字符串要簡單 類設計可使用關係運算符進行比較。
緣由是由於類函數重載(從新定義)了這些運算符
示例:
#include <iostream>
#include <string> //頭文件不一樣
int main(){
using namespace std;
string word="?ate"; //定義字符串對象
for(char ch='a';word!="mate";ch++)//循環
{
cout<<word<<endl;
word[0]=ch;//修改字符數組的首元素爲ch
}
cout<<"after loop ends,word is "<<word<<endl;
return 0;
}
程序說明:
word!="mate" 使用了關係運算符 左邊是一個string類的對象,右邊是一個c風格的字符串
string類重載運算符!=的方式可使用的條件:
1.至少有一個操做數爲string對象,另外一個操做數能夠是string對象,也能夠是c風格字符串
2.此循環不是計數循環 並不對語句塊執行指定的次數,相反 它根據實際的狀況來肯定是否中止。 此種狀況一般使用while循環
二 while循環
while循環是沒有初始化和更新部分的for循環 它只有測試條件和循環體
while (test-condition)
body
首先程序計算圓括號內測試條件表達式 若是表達式爲true 則執行body 若是表達式維false 則不執行
body代碼中必須有完成某種影響測試條件表達式的操做。 例如 循環能夠將測試條件中的變量加1或者從鍵盤輸入讀取一個新值。
while循環也是一種入口條件循環。所以 測試條件一開始便爲false 則程序將不會執行循環體。
while(name[i]!='\0')
測試數組中特定的字符是否是空值字符。
1.for與while
本質上二者是相同的
for(init-expression;test-expression;update-expression)
{ statement(s) }
能夠寫成以下的形式
init-expression;
while(test-expression)
{
statement(s)
update-expression;
}
一樣的
while(test-expression) body
能夠寫成
for(;test-expression;)
body
for循環須要3個表達式 技術上說,須要1條後面跟兩個表達式的語句。不過它們能夠是空表達式,只有兩個分好是必須的。
另外for循環中 省略測試表達式時 測試結果將是true 所以 for(;;) body 會一直執行下去
差異:
1.for 沒有測試條件 則條件爲true
2.for中可使用初始化語句聲明一個局部變量 可是while不能這樣作
3.若是循環體中包含continue語句 狀況將會有所不一樣。
一般若是將初始值,終止值和更新計數器的、都放在同一個地方,那麼要使用for循環格式
當在沒法預先知道循環將執行的次數時,程序員常使用while循環
當使用循環時 要注意下面的幾條原則
1.指定循環終止的條件
2.在首次測試以前初始化條件
3.在條件被再次測試以前更新條件
錯誤的標點符號
for循環和while循環都由用括號括起的表達式和後面的循環體(包含一條語句)組成。
語句塊 由大括號括起 分號結束語句
2.等待一段時間,編寫延時循環
clock()函數 返回程序開始執行後所用的系統時間
#include <iostream>
#include <ctime>//describes clock() function, clock_t loop
int main()
{
using namespace std;
cout<<"enter the delay time,in seconds: ";
float secs;
cin>>secs;
clock_t delay=secs * CLOCKS_PER_SEC;//轉化成clock ticks的形式 始終滴答形式
cout<<"starting\a\n";
clock_t start=clock();
while(clock()-start<delay)//等待時間 直到爲假時跳出循環
; // 空操做 空語句
cout<<"done \a\n";
return 0;
}
頭文件ctime (time.h) 提供了這些問題的解決方案。首先,定義了一個符號常量--CLOCKS_PER_SEC 該常量等於每秒鐘包含的系統時間單位數。所以,將系統時間除以這個值,能夠獲得秒數。或者將秒數乘以CLOCK_PER_SEC 能夠獲得以系統時間單位爲單位的時間。其次,ctime將clock_t做爲clock()返回類型的別名 ,這意味着能夠將變量聲明爲clock_t類型 編譯器將把它轉換爲long 、unxigned int 或者適合系統的其餘類型。
類型的別名
c++爲類型創建別名的方式有兩種。
一種是使用預處理器:
#define BYTE char
這樣預處理器將在編譯程序時用char 替換全部的BYTE,從而使BYTE稱爲char的別名
第二種是使用c++和c的關鍵字typedef來建立別名。例如要講byte做爲char的別名,
typedef char byte;
通用格式:typedef typeName aliasName; aliasName爲別名
兩種比較
示例:
#define FLOAT_POINTER float *
FLOAT_POINTER pa,pb;
預處理器置換將該聲明轉換爲以下的形式
float *pa,pb; 此時 pa爲指針 而pb不是指針 只是float類型
typedef 方法不會有這樣的問題 它可以處理更加複雜的類型別名 最佳選擇,有時候是惟一的選擇
不會建立新的類型 僅僅是這種類型的另外一個名稱
三 do-while循環
前面兩種是入口條件的循環 ,而這種事出口條件的循環 意味着這種循環將首先執行循環體,而後再斷定測試表達式,決定是否應該繼續執行循環。若是條件爲false 則循環終止。不然進入新的執行和測試。 此時循環體至少要循環一次。
do
body
while(test-expression);
一般狀況下 入口條件循環要比出口條件循環好 由於入口條件循環在循環開始以前對條件進行檢查,可是有時候須要do while更加合理 例如請求用戶輸入時,程序必須先得到輸入,而後對它進行測試
//dowhile.cpp
#include <iostream>
int main(){
using namespace std;
int n;
cout<<"enter number in the range 1-10 to find"<<endl;
cout<<"my favorite number\n";
do{
cin>>n;//接收鍵盤的輸入
}
while (n!=7);//當 輸入的值不等於7時 判別式爲真值 繼續循環 等於7就退出循環 進行下面的語句的執行
cout<<"yes,7 is my favorite.\n";
return 0;
}
奇特的for循環
int i=0;
for(;;)
{
i++;
cout<<i<<endl;
if(30>=i) break;
}
或者另一種變體
int i=0;
for(;;i++)
{
if(30>=i) break;
//do something
}
上述的代碼基於這樣的一個事實:for循環中的空測試條件被視爲true。 不利於閱讀 在do while中更好的表達它們的意思
int i=0;
do{
i++;
//do something
}while(30>=i);
第二個例子的轉化
while(i<30)
{
//do something;
i++;
}
4.基於範圍的for循環(c++11)
c++11新增的一種循環
基於範圍 range-based的for循環:對數組(或容器類,如vector和array)的每一個元素執行相同的操做
double prices[5]={4.99,10.99,6.87,7.99,8.49};
for(double x:prices)
cout<<x<<std::endl;
其中x最初表示數組prices的第一個元素。顯示第一個元素後,不斷執行循環,而x依次表示數組的其餘元素。所以,上述代碼顯示所有5個元素,每一個元素佔據一行。總之,該循環顯示數組中的每一個值,要修改數組的元素,須要使用不一樣的循環變量的語法:
for(double &x:prices)
x=x*0.80;
示例1
#include <iostream>
int main(){
using namespace std;
double prices[5]={4.99,10.99,6.87,7.99,8.49};
for(double x:prices) //x最初表示首元素 以後x依次表示數組的其餘元素
cout<<x<<endl;
return 0;
}
將上面的代碼中的循環更改成for(double &x:prices)
x=x*0.80; //價格變爲8折
&代表x是一個引用變量(而不是取地址),這種聲明讓接下來的代碼可以修改數組的內容,而第一種語法不能。
也可使用基於範圍的for循環
for(int x:{3,5,2,8,6})
cout<<x<<" ";
5.循環和文本輸入
系統中最多見的 最重要的任務:逐字符地讀取來自文件或鍵盤的文本。
例如想編寫一個可以計算輸入中的字符數,行數和字數的程序,c++與c在i/o工具不盡相同 cin對象支持3中不一樣模式的單字符輸入,其用戶接口各部相同。
1.使用原始的cin進行輸入
若是程序要使用循環來讀取來自鍵盤的文本輸入,則必需要知道什麼時候中止讀取。一種方法就是選擇某個字符 做爲哨兵字符 將其做爲中止標記。
示例:
//testin1.cpp
#include <iostream>
int main(){
using namespace std;
char ch;
int count=0;
cout<<"enter characters;enter # to quit:\n";
cin>>ch;
while(ch!='#')//以#字符爲終止符 若是輸入中不是# 就繼續執行循環
{
cout<<ch;
++count;
cin>>ch;//讀取下一個字符 很重要 若是沒有此條,就會重複處理第一個字符
}
cout<<endl<<count<<"characters read\n";
return 0;
}
程序說明
此循環遵循了前面的循環原則,結束條件爲最後讀取的字符爲#,在循環以前讀取一個字符進行初始化,而經過循環體結尾讀取下一個字符進行更新。
在輸入時 能夠輸入空格和回車 可是在輸出時,cin會將輸入的字符中的空格和換行符忽略。因此輸入的空格或者回車符沒有被回顯,也沒有被包括在計數內。
更加複雜的是,發送給cin的輸入被緩衝,這意味着只有在用戶按下回車鍵後,他輸入的內容纔會被髮送給程序。這就是在運行該程序時,能夠在#後面輸入字符的緣由。按下回車鍵後,這個字符序列講被髮送給程序,可是程序在遇到#字符後將結束對輸入的處理。
2.使用cinget(char)進行補救
一般,逐個字符讀取輸入的程序須要檢查每一個字符,包括空格、製表符和換行符。cin所屬的istream類(在iostream中定義)中包含一個可以知足這種要求的成員函數。
cin.get(ch)讀取輸入中的下一個字符(即便它是空格),並將其賦值給變量ch。使用這個函數調用替換cin>>ch,能夠修補上面程序的問題
//testin1.cpp
#include <iostream>
int main(){
using namespace std;
char ch;
int count=0;
cout<<"enter characters;enter # to quit:\n";
cin.get(ch); //會顯示空格 製表符等其餘的字符
while(ch!='#')
{
cout<<ch;
++count;
cin.get(ch);
}
cout<<endl<<count<<"characters read\n";
return 0;
}
程序說明:可能的誤解:c語言中 cin.get(ch)調用將一個值放在ch變量中,這意味着將修改該變量的值
c中要修改變量的值,必須將變量的地址傳遞給函數。
在上例中 cin.get() 傳遞的是ch 而不是&ch 在c中此代碼無效,可是在c++中有效,只要函數將參數聲明爲引用便可。
引用在c++中對比c新增的一種類型。頭文件iostream 將cin.get(ch)的參數聲明爲引用類型,所以該函數能夠修改其參數的值。
3.使用哪個cin.get()
char name[arsize];
...
cout<<"enter your name:\n";
cin.get(name,arsize).get();//至關於兩個函數的調用。 cin.get(name,arsize);cin.get();
cin.get 的一個版本接收兩個參數 :數組名 (地址)和arsize (int 類型的整數) 數組名的類型爲 char *
cin.get() 不接收任何參數
cin.get(ch) 只有一個ch參數
在c中不可想象 參數數量不夠 或過多 會形成函數的出錯
可是在c++中,這種被稱爲函數重載的oop特性 容許重載建立多個同名函數,條件是它們的參數列表不一樣。若是c++中使用cin.get(name,arsize),則編譯器將找到使用char* 和int做爲參數的cin.get()版本。 若是沒有參數,則使用無參的版本。
4.文件尾條件
若是正常的文本中#正常的字符 那麼用#來用做結尾符不會合適
若是輸入來自於文件,可使用一種功能更增強大的技術 EOF 檢測文件尾。
c++輸入工具和操做系統協同工做,來檢測文件尾並將這種信息告知程序。
cin和鍵盤輸入 相關的地方是重定向 < 或者 >符號
操做系統容許經過鍵盤來模擬文件尾條件。
在unix中 能夠在行首按下CTRL+D來實現。
在windows命令提示符下,能夠在任意位置按CTRL+Z和enter
不少的pc編程環境都講CTRL+z視爲模擬的EOF
檢測EOF的方法原理:
檢測EOF後,cin將兩位(eofbit和failbit)都設置爲1;
查看eofbit是否被設置 使用成員eof()函數
若是檢測到EOF,則cin.eof()將返回bool值true,不然返回false。
一樣,若是eofbit或者fialbit被設置爲1,則fail()成員函數返回true,不然返回false。
eof()和fial()方法報告最近的讀取結果。所以應將cin.eof() 或者cn.fail()測試放在讀取後
#include <iostream>
int main()
{
using namespace std;
char ch;
int count=0;
cin.get(ch);
while(cin.fail()==false)//判斷cin.fail()的值 是否被設置,若是ctrl+z 和enter 已經按下 則返回true 條件不符合,跳出循環
{
cout<<ch;
++count;
cin.get(ch);
}
cout<<endl<<count<<"characters read\n";
return 0;
}
程序說明:windows 7系統上運行該程序,所以能夠按下ctrl+z和回車鍵來模擬EOF條件,在unix和類unix(包括linux)等系統張,用戶能夠按ctrl+z組合鍵將程序掛起,而命令fg恢復程序的執行。
經過使用重定向,能夠用此程序來顯示文本文件。並報告它包含的字符數 主要在unix中測彷佛
1.EOF結束輸入
cin方法檢測到EOF時,將設置cin對象中一個指示EOF條件的標記。設置這個標記後,cin將不讀取輸入,再次調用cin也無論用。
上面的狀況 是對文件的輸入有道理,由於程序不該該讀取超出文件尾的內容
對於鍵盤的輸入,有可能使用模擬EOF來結束循環,可是稍後還要讀取其餘輸入。
cin.clear()方法用來清除EOF標記,使輸入繼續進行。
在有些系統中CTRL+Z實際上會結束輸入和輸出。 而cin.clear()將沒法恢復輸入和輸出。
2.常見的字符輸入作法
每次讀取一個字符,直到遇到EOF的輸入循環的基本設計以下:
cin.get(ch);
while(cin.fail()==false) //這裏能夠用!邏輯運算符 去掉等號 while(!cin.fail())
{
...
cin.get(ch);
}
方法cin.get(char)的返回值是一個cin對象。然而,istream類提供了一個可將istream對象轉換爲bool值的函數;
當cin出如今須要bool值的地方(如在while循環的測試條件中)時,該轉換函數將被調用。另外,若是最後一次讀取成功了,則轉換獲得的bool值爲true;不然爲false。
意味着能夠改寫爲
while(cin)//while input is successful
這樣比!cin.fail()或者!cin.eof()更通用,由於它能夠檢測到其餘失敗的緣由,如磁盤故障
最後,因爲cin.get(char)的返回值爲cin,所以能夠將循環精簡成這種格式:
while(cin.get(sh))
{
...
}
這樣 cin.get(char)只被調用了一次,而不是兩次:循環前一次、循環結束後一次。爲判斷循環測試條件,程序必須首先調用cin.get(ch).若是成功,則將值放入ch中。而後,程序得到函數調用的返回值,即cin。以後對cin進行bool轉換,若是輸入成功,則結果爲true,不然爲false。三條指導原則 所有被放在循環條件中。
5.另外一個cin.get()版本
舊式的c代碼 i/o函數 getchar()和putchar()函數 仍然適用,只要像c語言中那樣包含頭文件stdio.h或者新的cstdio便可。
也可使用istream和ostream類中相似功能的成員函數。
cin.get() 不接受任何參數 會返回輸入中的下一個字符
ch=cin.get();
與getchar()類似,將字符編碼做爲int類型返回;
cn.get(ch)則會返回一個對象,而不是讀取的字符。
cout.put(ch) 顯示字符 此函數相似於c中的putchar() 只不過參數類型爲char 而不是int
put()最初只有一個原型 put(char) ,能夠傳遞一個int參數給它,該參數將被強制轉換爲char。c++標準還要求只有一個原型。而後c++的有些實現都提供了3個原型
參數分別爲char signed char unsigned char 。這些實現中若是給put()傳遞一個int參數將致使錯誤消息,由於轉換int的方式不止一種,若是使用顯式強制類型轉換的原型(cin.put(char(ch)))可使用int參數
成功使用cin.get() 須要瞭解EOF條件。
當函數到達EOF時,將沒有能夠返回的字符。相反,cin.get()將返回一個用符號常量EOF表示的特殊值。該常量是在頭文件iostream中定義的。EOF值必須不一樣於任何有效的字符值,以便程序不會將EOF於常規字符混淆。一般EOF被定義爲值-1,由於沒有ascii碼爲-1的字符,但並不須要知道它實際的值,在程序中使用EOF便可。
示例:
char ch; 替換爲 int ch;
cin.get(ch); 替換爲 ch=cin.get();
wihile(cin.fail()==false) 替換爲ch!=EOF
{
cout<<ch; 替換爲:ch.put(ch);
++count;
cin.get(ch); 替換爲:ch=cin.get();
}
上面代碼的功能是 若是ch是一個字符,則循環將顯示它,若是ch爲EOF 則循環將結束
須要知道的是 EOF不表示輸入中的字符,而是指出沒有字符。
//testin4.cpp
#include <iostream>
int main(void)
{
using namespace std;
int ch;
int count=0;
while((ch=cin.get())!=EOF) //獲取字符 並將字符的int型的編碼賦值給ch 而後再判斷與-1(EOF)是否相等
{
cout.put(char(ch)); //將int型編碼的ch 強制轉換爲char類型 而且輸出
++count;
}
cout<<endl<<count<<"characters read\n";
return 0;
}
注意,有些系統 若是對EOF不徹底支持,若是使用cin.get()來鎖住屏幕直到能夠閱讀它,將不起做用,由於檢測EOF時將禁止進一步讀取輸入。可使用循環計時來使屏幕停留一段時間。 也可使用cin.clear來重置輸入流。
儘可能使用cin.get(char) 這種返回cin對象的方式,get成員函數的主要用途是可以將愛那個stdio.h的getchar()和putchar()函數轉換爲iostream的cin.get()和cout.put()方法。只要用頭文件iostream替換爲stdio.h 並用做用類似的方法替換全部的getchar()和putchar()便可。
6.嵌套循環和二維數組
1. int a[4][5]二維數組
a[0] 爲二維數組的首元素,而首元素又是包含5個元素的數組
2.for(int row=0;row<4;row++) //行
{
for(int col=0;col<5;++col)//列
cout<<a[row][col]<<"\t";
cout<<endl;
}
3.初始化二維數組
int a[2][3]={
{15,25,35},{17,28,49}
}
也能夠 int a[0]={15,25,35}
int a[1]={17,28,49}
4.使用二維數組
//nested.cpp
#include <iostream>
const int Cities=5;
const int years=4;
int main()
{
using namespace std;
const char * cities[Cities]= //指針數組
{
"girbble city","gribbletown","new grebble","san gribble","gribble vista"
};
int maxtemps[years] [Cities]= //二維數組
{
{96,100,87,101,105},{96,98,91,107,104},{97,101,93,108,107},{98,103,95,109,108}
};
cout<<"maximum temperatures for 2008 -2011\n\n";
for(int city=0;city<Cities;++city)
{
cout<<cities[city]<<"\t";
for(int year=0;year<years;++year)
cout<<maxtemps[year][city]<<"\t";
cout<<endl;
}
return 0;
}
在此例中,可使用char數組的數組,而不是字符串指針數組
char cities[Cities][25]
{
"Gribble City",
"Gribbletown",
"New Gribble",
"San Gribble",
"Gribble Vista"
};
此種方法將所有5個字符串的最大長度限制爲24個字符。指針數組存儲5個字符串的地址,而使用char數組的數組時,將5個字符串分別賦值到5個包括25個元素的char數組中。所以,從存儲空間的角度說,使用指針數組更爲經濟;可是char數組的數組更容易修改其中的一個字符串。
方法2:
可使用string對象數組 而不是字符串指針數組
const string cities[Cities]={
"Gribble City",
"Gribbletown",
"New Gribble",
"San Gribble",
"Gribble Vista"
}
若是但願字符串是可修改的,則應該省略限定符const。使用string 對象數組時,初始化列表和用於顯示字符串的for循環代碼與前兩種方法中相同。在但願字符串是可修改的狀況下,string類自動調整大小的特性將使這種方法比使用二維數組更爲方便。
第六章 分支語句
1.
if(test-condition)
statement
2.
if(test-condition)
statement1
else
statement2
3.格式化if else 語句
if(){}
else{}
或者
if(){} else if(){} else{}
tips:條件運算符和錯誤防範
variable==value 反轉爲value==variable
以此來捕獲將相等運算符誤寫爲賦值運算符的錯誤
意思就是 if(3==a) 能夠避免寫成if(3=a) 編譯器會生成錯誤的信息,由於它覺得程序員試圖將一個值賦值給一個字面值(3老是等於3,而不是將另外一個值賦值給它)
假如省略了其中的一個等號 if(a=3) 就不會出現錯誤信息 沒有 if(3=a) 更好
2.邏輯表達式
a || 或運算符 比關係運算符的優先級要低
此運算符是一個順序點 sequence point 也就是說 先修改左側的值,再對右側的值進行斷定c++11側說法是 運算符左邊的子表達式先於右邊的子表達式
若是左側的表達式爲true 則c++將不會去斷定右側的表達式
cin>>ch;
if(ch=='y'||ch=='Y')
cout<<"You were warned!\a\a\n";
else if(ch=='n'||ch=='N')
cout<<"A wise choice ... bye\n";
else ....
程序只讀取一個字符,因此只讀取響應的第一個字符。這意味着用戶能夠用NO! 而不是N 進行回答,程序將只讀取N。然而,若是程序後面再讀取輸入時,將從O開始讀取。
b && 邏輯與表達式 同或運算符同樣 也是順序點 也是左側肯定true後,不看右側
用&&來設置取值範圍
&&運算符還容許創建一系列 if else if else語句,其中每種選擇都對應於一個特定的取值範圍。
例如
if(age>=85&&age<=100)
else if(age>=70&&age<85)
...
c 邏輯NOT運算符!
取反 優先級高於全部的關係運算符和算數運算符
d 邏輯運算符細節
c++邏輯or和邏輯and運算符的優先級都低於關係運算符。
c++確保程序從左向右進行計算邏輯表達式,並在知道答案後馬上中止
e 其餘表示方式
並非全部的鍵盤都提供了用做邏輯運算符的符號 因此 c++標準提供了另外一種表示方式
就要使用到and or not 等c++保留字 可是不是關鍵字 若是c語言想將其用做運算符 須要包含頭文件iso646.h
3.字符函數庫cctype
一個與字符相關的 很是方便的函數軟件包 主要用來測試字符類型
頭文件 cctype 老式的爲ctype。h
cin.get(ch);
if(isalpha(ch)) 判斷是否爲字母字符
4.?:運算符 三目運算符
5>3?10:12
if(5>3)
return 10;
else
return 12;
示例2:
(i<2)? !i ? x[i] : y : x[1];
嵌套三目運算符
5.switch
swithc(integer expression)//其中括號中的表達式的值爲label1 或者label2 或者其餘(default)
{
case label1:statement(s);
break; //跳出循環
case label2:
case label3:statement(s);//2和3同樣 都會執行後面的語句
default:statement(s)
}
5.1將枚舉量用做標籤
enum{red,orange,yellow,green,blue};//枚舉量
int main()
{
int code;
cin>>code;
switch(code)
{
case red:cout<<"red";
break;
case orange:cout<<"orange";
break;
defalut:....
}
}
5.2 switch 和if else 的區別
if else 更加通用 並且能夠處理區間
if(age>17&& age<50)
6.break和continue 語句
continue 是直接跳到循環開始處 開始下一輪循環 不會跳過循環的更新表達式 for循環中 continue語句使程序直接跳到更新表達式處,而後跳到測試表達式處。
可是對於while循環 continue會直接跳到測試表達式處 在while循環中 位於continue以後的更新表達式都將被跳過
break是跳出循環 結束包含它的循環
goto label; 直接將程序跳到label處
7.讀取數字的循環
讀入數字
int n;
cin>>n;
若是是字符不是數字 會發生以下的狀況
n的值不變
不匹配的輸入將被留在輸入隊列中
cin對象中的一個錯誤標記被設置
對cin方法的調用將返回false 若是被轉換爲bool類型
方法返回false 意味着能夠用非數字輸入來結束讀取數字的循環。非數字輸入設置錯誤標記意味着必須重置該標記,程序才能繼續讀取輸入。
clear()方法重置錯誤輸入標記,同時也重置文件尾 EOF條件 輸入錯誤和EOF都會致使cin返回false
示例
//cinfish.cpp
#include <iostream>
const int Max=5;
int main()
{
using namespace std;
double fish[Max];
cout<<"please enter the weights of your fish.\n";
cout<<"fish #1: ";
int i=0;
while(i<Max&&cin>>fish[i])// 左側的表達式爲假 則不會判斷右側的表達式,右側的表達式判斷的同時會將結果存儲到數組中
{
if(++i<Max) //執行++i i的值會變化
cout<<"fish #"<<i+1<<": ";
}
double total=0.0;
for(int j=0;j<i;j++)
total+=fish[j];
if(i==0)
cout<<"no fish\n";
else
cout<<total/i<<"=average weight of " <<i<<" fish\n";
cout<<"done.\n";
return 0;
}
上面的程序當輸入不是數字時,該程序將不在讀取輸入隊列,程序將拒絕輸入;若是想繼續讀取輸入,則須要重置輸入
當輸入錯誤的內容時:採起的3個步驟
1.重置cin以接受新的輸入
2.刪除錯誤輸入 (如何刪除錯誤的輸入) 程序必須先重置cin 而後才能刪除錯誤輸入
3.提示用戶再輸入
//cingolf.cpp
#include <iostream>
const int Max=5;
int main()
{
using namespace std;
int golf[Max];
cout<<"please enter your golf scores.\n";
cout<<"you must enter "<<Max<<" rounds.\n";
int i;
for(i=0;i<Max;i++)
{
cout<<"round #"<<i+1<<": ";
while(!(cin>>golf[i]))//用戶輸入數字 則cin表達式將爲true 則將後面的值存入到數組中,若是輸入的是非數字值,則cin爲false 則值不會存入到數組中
{
cin.clear();//若是沒有此語句,則程序會拒絕繼續讀取輸入
while(cin.get()!='\n')//讀取行尾以前的全部輸入,從而刪除這一行中的錯誤輸入;另外一種方法是讀取到下一個空白字符,這樣將每次刪除一個單詞,而不是一次刪除整行。
continue;
cout<<"please enter a number: ";
}
}
double total=0.0;
for(int i=0;i<Max;i++)
total+=golf[i];
cout<<total/Max<<"=average scores " <<Max<<" rounds\n";
cout<<"done.\n";
return 0;
}
8.簡單文件的輸入和輸出
1.文本i/o和文本文件
cin輸入,會將輸入看做一系列的字節,每一個字節被解釋爲字符編碼,無論目標的數據類型是什麼,都被看做字符數據,而後cin對象負責將文本轉換爲其餘的類型。
輸入38.2 15.5
int n;
cin>>n 會不斷的讀取,直到遇到非數字字符, 38被讀取後 句點將成爲輸入隊列中的下一個字符
double x;
cin>>x; 不斷的讀取 直到遇到不屬於浮點數的字符。 38.2將會被讀取,空格將成爲輸入隊列中的下一個字符
char word[50];
cin>>word;
不斷讀取,直到遇到空白字符, 38.5被讀取,而後將這4個字符的編碼存儲到數組word中。並在末尾加上一個空字符。
char word[50];
cin.getline(word,50);
不斷讀取,直到遇到換行符(示例中少於50個字符),全部字符被存儲到word中 包括空格,並在末尾加上一個空字符。換行符被丟棄,輸入隊列中的下一個字符是下一行中的第一個字符。這裏不須要轉換。
對於輸入 將執行相反的轉換。即整數被轉換爲數字字符序列,浮點數被轉換爲數字字符和其餘字符組成的字符序列 字符數據不須要作任何的轉換。
輸入一開始都是文本,因此控制檯輸入的文件版本都是文本文件愛你,即每一個字節都存儲了一個字符編碼的文件。
2.寫入到文本文件中 相對應於寫入到控制檯上(屏幕 對應於cout)
cout用於控制檯輸出的基本事實
必須包含頭文件iostream
頭文件iostream定義了一個用處理可輸出的ostream類
頭文件iostream聲明瞭一個名爲cout的ostream變量(對象)
必須指明名稱空間std;
結合使用cout和運算符<<來顯示各類類型的數據
文件輸出與上述類似
必須包含頭文件fstream
頭文件fstream定義了一個用於處理輸出的ofstream類
須要聲明一個或多個ofstream變量(對象),並本身命名
指明名稱空間std
須要ofstream對象與文件關聯起來,爲此,方法之一是使用open()方法
使用完文件後,應好似用方法close()將其關閉
可結合使用ofstream對象和運算符<<來輸出各類類型的數據
cout爲ostream已經定義好的 因此不須要從新定義
而ofstream對象沒有已經定義好的對象 因此須要本身聲明此脆響 並同文件相關聯
ofstream outfile;
ofstream fout; 定義了兩個對象 都屬於ofstream類
關聯文件
outfile.open("fish.txt"); //關聯文件 fish.txt
char filename[50];
cin>>filename;
fout.open(filename); //關聯文件 filename變量存儲的值做爲文件名
使用方法:
double wt=125.8;
outfile<<wt; //將wt數據寫入到上面關聯的文件中 fish.txt
char line[81]="objects are closer than they appear";
fout<<line<<endl; //將line數組中的內容輸出到fout對象關聯的文件中去
聲明一個ofstream對象並將其同文件關聯起來後,即可以像使用cout那樣使用它。全部可用於cout的操做和方法均可以用於ofstream對象
主要步驟:
1.包含頭文件fstream
2.建立一個ofstream對象
3.將該ofstream對象同一個文件關聯起來。
4.就像使用cout那樣使用該ofstream對象
//outfile.cpp
#include <iostream>
#include <fstream>
int main()
{
using namespace std;
char automobile[50];
int year;
double a_price;
double d_price;
ofstream outfile;
outfile.open("carinfo.txt");//若是在當前目錄下沒有此文件怎麼辦呢?自動建立 ;若是已經存咋這個文件,默認狀況下會刪除原來的內容(將此文件的長度截短爲0),寫入新的內容。17章可根據參數修改 此默認設置
cout<<"enter the make and model of automobile: ";
cin.getline(automobile,50);//讀取到換行符爲止
cout<<"enter the model year: ";
cin>>year;
cout<<"enter the original asking price: ";
cin>>a_price ;
d_price=0.913*a_price;
cout<<fixed;
cout.precision(2);
cout.setf(ios_base::showpoint);//顯示固定格式
cout<<"make and model: "<<automobile<<endl;
cout<<"year: $"<<year<<endl;
cout<<"was asking $"<<a_price<<endl;
cout<<"now asking $"<<d_price<<endl;
outfile<<fixed;
outfile.precision(2);//格式化方法
outfile.setf(ios_base::showpoint);//格式化方法
outfile<<"make and model: "<<automobile<<endl;
outfile<<"year: "<<year<<endl;
outfile<<"was asking $" <<a_price <<endl;
outfile<<"now asking $"<<d_price <<endl;
outfile.close();//不須要使用文件名做爲參數
return 0;
}
全部的cout方法均可以用outfile對象使用
關聯文件後,須要關閉此文件的關聯
3.讀取文本文件 相似於cin 即istream類和ifstream類對應
cin 將控制檯(鍵盤的內容輸入) 存入到變量中
ifstream類定義的對象 從文本文件中讀取內容到指定的變量中
首先看控制檯:
包含iostream頭文件
此頭文件定義了一個用處理輸入的istream類
頭文件iostream聲明瞭一個名爲cin的istream變量 對象
指明命名空間
結合cin和get方法來讀取一個字符 使用cin和getline來讀取一行字符
cin 和eof()、fial()方法來判斷輸入是否成功。
cin自己用做測試條件,若是最後一個讀取操做成功,它將轉換爲bool的值,true或者false
文件的輸入與上面的cin相似
頭文件fstream
頭文件fstream定義了一個用於處理輸入的ifstream類
須要聲明一個或多個ifstream變量(對象),並以本身喜歡的方式對其進行命名,條件是遵照經常使用的命名規則。
指明名稱空間std
將ifstream對象與文件關聯起來。方法之一是使用open方法
使用完文件後,須要使用close()方法將其關閉
ifstream對象和運算符>>來讀取各類類型的數據
ifstream和eof() fail()等方法來判斷輸入是否成功
ifstream對象自己被用做測試條件時,若是最後一個讀取操做成功,它將被轉換爲布爾值bool
示例:
ifstream infile;
ifstream fin;
infile.open("bowling.txt");
char filename[50];
cin>>filename;
fin.open(filename);
double wt
infile>>wt;
char line[81];
fin.getline(line,81);
若是試圖打開一個不存在的文件用於輸入,則這種錯誤將致使後面使用ifstream對象進行輸入時失敗,檢查文件是否被成功打開的首先方法是使用is_open(),爲此可使用相似於下面的代碼:
infile.open("bowling.txt");
if(!infile.is_open())
{
exit(EXIT_FAILURE);
}
若是文件被成功打開,方法is_open()將返回true;不然返回false
exit()函數的原型是在頭文件cstdlib中定義的。在該頭文件中,還定義了一個用於同操做系統通訊的參數值EXIT_FAILURE 函數exit()終止程序。
若是編譯器不支持is_open()函數 可使用比較老的方法good()來代替。可是此函數在檢查問題方面沒有新的函數is_open()那麼普遍。
示例
//sumafile.cpp
#include <iostream>
#include <fstream>
#include <cstdlib>
const int SIZE=60;
int main()
{
using namespace std;
char filename[SIZE];
ifstream infile;//定義對象
cout<<"enter name of data file: ";
cin.getline(filename,SIZE);//從文件中獲取一行數據 60個字符
infile.open(filename);//關聯指定的文件
if(!infile.is_open())//判斷文件是否存在 若是不存在則提示 而且退出
{
cout<<"could not open the file"<<filename<<endl;
cout<<"program terminating.\n";
exit(EXIT_FAILURE);//根據EXIT_FAILURE的值 退出返回
}
double value;
double sum=0.0;
int count=0;
infile>>value;//從文件獲取值 存入變量
while(infile.good())//判斷獲取的數據是否爲double類型
{
++count;
sum+=value;
infile>>value;//獲取下一個值
}
if(infile.eof())//判斷是否到文件結尾
cout<<"end of file reached.\n";
else if(infile.fail())//判斷是否數據匹配
cout<<"input terminated by data mismatch.\n";
else
cout<<"input terminated for unknown reason.\n";
if(count==0)//
cout<<"no data processed.\n";
else//最後輸出各個變量的值
{
cout<<"items read: "<<count<<endl;
cout<<"sum: "<<sum<<endl;
cout<<"average: "<<sum/count<<endl;
}
infile.close();
return 0;
}
1.程序說明 設置中文路徑名稱可用
setlocale(LC_ALL,"Chinese-simplified")
2.注意文件的擴展名
1.txt.txt
3.加入絕對路徑
infile.open(「c://source/1.txt」);
4.文件讀取循環的正確設計。讀取文件時,有幾點須要檢查,首先,程序讀取文件時要超過EOF,若是遇到EOF 則方法eof會返回true;其次類型不匹配 fail返回true;若是最後一次讀取文件受損或因噶進故障,bad會返回true 因此不要分別檢查這些問題,用good()方法一次性檢查就能夠了。若是沒有任何問題則返回true
注意上面檢測的順序,首先是eof 以後是fail 再以後是bad
同cin相同
cin>>value; //循環以前讀入
while(){
...
cin>>value; 循環中讀入
}
同上
infile>>value;//循環以前讀入
while(infile.good())
{
...
infile>>value;//循環以後讀入
}
能夠合併爲一個判斷的表達式
while (infile>>value)
{
statements;
}
第七章 函數 ----c++的編程模塊
函數原型 prototype
函數定義 definition
調用函數 calling
1.定義函數
有返回值和無返回值的函數
int functionName(parameterList) 有返回值
{
statements(s)
return value; 必須存在
}
無返回值
void functionname(parameterlist)
{
statements(s);
return; 無關緊要 不返回任何值
}
2.函數原型和調用
系統庫提供的函數的原型通常包含在include文件中 頭文件中。
提供函數原型的緣由以下:
a 原型描述了函數到編譯器的接口,它將函數返回值的類型以及參數的類型和數量告訴編譯器。
double volume=cube(side);
首先,原型告訴編譯器,cube有一個double參數 ,若是程序沒有提供這樣的參數,原型將讓編譯器可以捕獲這種錯誤。其次,cube函數完成計算後,將把返回值放置在指定的位置--多是cpu寄存器,也多是內存中。而後調用函數將從這個位置取得返回值。
由於原型指出了cube的類型爲double 所以編譯器知道應該檢索多少個字節以及如何解釋它們。
另外一個方面是,若是沒有原型,編譯器就會在程序中查找,致使效率不高,編譯器在搜索文件的剩餘部分時將必須中止對main()的編譯
另外一個問題是,函數可能並不在文件中,c++容許將一個程序放在多個文件中,單獨編譯這些文件,而後再將他們組合起來。
3.原型的語法
函數原型是一條語句,所以必須以分號結束。
複製函數頭部加上分號 就是原型
原型中能夠沒有變量名,可是必須有類型
4.原型的功能 c++中必須提供函數的原型
編譯器正確處理函數的返回值
編譯器檢查使用的參數數目是否正確
編譯器檢查使用的參數類型是否正確。若是不正確,則轉換爲正確的類型
調用函數cube (2) 原型爲cube(double)
因爲原型中指定了double類型 因此會將整型的2轉換爲2.0再參加運算
可是若是反過來 cube(int ) 調用時使用cube(2.7854)
原型爲int型 轉換時會將浮點的類型轉換爲int 會致使數據的丟失
二 函數參數和按值傳遞
1.按值傳遞時,不會改變實參的數值
將實參複製一個副本 成爲形參的值,在函數中計算的是形參的數值
2.多個參數
逗號分隔多個參數
和一個參數同樣,原型中沒必要與定義中的變量名相同,並且能夠省略
可是在原型中提供變量名使得程序更加容易理解
三函數和數組
示例:int sum_arr(int arr[],int n) 爲原型
調用:int sum_arr(int arr[],int n)
也可使用int sum_arr(int * arr,int n)
1.函數如何使用指針來處理數組
將數組名視爲指針
cookies==&cookies[0]
首先,數組聲明使用數組名來標記存儲位置;其次,對數組名使用sizeof將獲得整個數組的長度 以字節爲單位ie;第三,將地址運算符&用於數組名時,將返回整個數組的地址
原型:int sum_arr(int * arr,int n) 使用int arr[] 是相同的 可是後者提醒 指針指向數組的第一個元素
調用:int sum_arr(cookies,arsize)
兩個恆等式:
arr[i]==*(ar+i)
&arr[i]==ar+i
將指針(包括數組名)加1 實際上加上了一個指針指向的類型的長度 字節爲單位ie 對於便利數組而言,使用指針加法和數組下標時等效的
2.將數組做爲參數意味着什麼?
上例中 實際上傳遞的是指向數組的指針,而不是整個數組。
當傳遞常規變量是,使用的是該變量的拷貝;可是傳遞數組時,函數將使用原來的數組。可是傳遞數組指針時,使用的是原來的數組,並非數組的copy
雖然節省了資源,可是同時帶來了對原始數據被破壞的風險。
可使用const的限定符這種解決辦法。
cookies和arr指向同一個地址,可是sizeof cookies 的值是32 而sizeof arr爲4 這是由於sizeof cookies 是整個數組的長度,而sizeof arr只是指針變量自己的長度。順便說,這也是必須顯式傳遞數組長度,而不能在sum_arr()中使用sizeof arr的緣由 指針並無指出數組的長度
在原型中不要使用方括號來傳遞數組的長度
void fillarray(int arr[size])
3.更多數組函數示例
//7.7 arrfun3.cpp
#include <iostream>
const int Max=5;
int fill_array(double ar[],int limit);//原型 fill_array
void show_array(const double ar[],int n);//原型 show_array
void revalue(double r,double ar[],int n);//原型 revalue
int main(){
using namespace std;
double properties[Max];
int size=fill_array(properties,Max);//調用填充函數 注意參數
show_array(properties,size);//調用顯示函數
if(size>0)//大於0 說明fill_array函數返回的值是 填充成功的數量
{
cout<<"enter revaluation factor: ";
double factor;
while(!(cin>>factor)) //輸入的值各類緣由不成功 每次讀取一個字符
{
cin.clear();//重置輸入
while(cin.get()!='\n') //再次獲取輸入 若是不等於換行符 則繼續讀取 一直到換行符以前
continue;
cout<<"bad input,please enter a number: ";
}
revalue(factor,properties,size);//從新賦值
show_array(properties,size);//再次調用顯示函數
}
cout<<"donw.\n";
cin.get();
cin.get();//等待 顯示
return 0;
}
int fill_array(double ar[],int limit)//填充數組的實現
{
using namespace std;
double temp;
int i;
for(i=0;i<limit;i++)
{
cout<<"enter value #"<<(i+1)<<": ";
cin>>temp;
if(!cin)
{
cin.clear();
while(cin.get()!='\n')
continue;
cout<<"bad input;input process terminated .\n";
break;
}
else if(temp<0)//輸入的是賦值則跳出循環
break;
ar[i]=temp;//數組賦值
}
return i;
}
void show_array(const double ar[],int n)//顯示數組中各個元素的值 這裏的const 用來保證不會修改原始的數組;
{
using namespace std;
for(int i=0;i<n;i++)
{
cout<<"property #"<<(i+1)<<":$";
cout<<ar[i]<<endl;
}
}
void revalue(double r,double ar[],int n)//從新賦值
{
for(int i=0;i<n;i++)
ar[i]*=r;
}
程序說明 關於void show_array(const double ar[],int n);
指針ar不能修改該數據,也就是說可使用像ar[0]這樣的值,但不能修改,注意,這並不意味着原始數組必須是常量,而只是意味着不能在show_array()函數中使用ar來修改這些數據,所以,show_array()將數組視爲只讀數據。
假設無心間在show_array()函數中執行了下面的操做,從而違反了這種限制:
ar[0]+=10; 會提示錯誤
c++聲明const double ar[] 解釋爲const double * ar 所以該聲明其實是說 ar指向的是一個常量值。
首先考慮的是經過數據類型和設計適當的函數來處理數據 而後將這些函數組合成一個程序。 叫作自下而上的程序設計
c語言爲過程性編程 傾向於從上而下的程序設計
4.使用數組區間的函數
處理數組的c++函數,必須將數組中的數據種類,數組的起始位置和數組中元素數量提交給它;
另外一種處理數組的方法,即指定元素區間range 能夠經過傳遞兩個指針來完成。一個指針標識數組的開頭 另外一個指針標識數組的尾部
sum=sum_arr(cookies,cookies+arsize);
sum=sum_arr(cookies,cookies+3);
sum=sum_arr(cookies+4,cookies+8);
//arrfun4.cpp
#include <iostream>
const int arsize=8;
int sum_arr(const int * begin,const int * end);
int main()
{
using namespace std;
int cookies[arsize]={1,2,4,8,16,32,64,128};
int sum=sum_arr(cookies,cookies+arsize);
cout<<"total cookies eaten: "<<sum<<endl;
sum=sum_arr(cookies,cookies+3);
cout<<"first three eaters ate "<<sum<<"cookies.\n";
sum=sum_arr(cookies+4,cookies+8);
cout<<"last four eaters ate "<<sum<<" cookies.\n";
return 0;
}
int sum_arr(const int * begin,const int * end)
{
const int * pt;
int total=0;
for(pt=begin;pt!=end;pt++)
total=total+*pt;
return total;
}
pt設置爲指向要處理的第一個元素 begin指向的元素的指針,並將*pt 加入到total中,而後循環經過遞增操做來更新pt。使之指向下一個元素。只要pt不等於end 這個過程就會繼續。
end-begin爲整數值 等於數組的元素數目
5.指針和const
兩種不一樣的方式將const關鍵字用於指針。
第一是讓指針指向一個常量對象,這樣能夠防止使用該指針來修改所指向的值
第二是將指針自己聲明爲常量,能夠防止改變指針指向的位置
int age=39;
const int * pt=&age;
pt指向一個const int的值,因此不能用pt來修改這個值, 換句話說就是* pt的值爲const 不能被修改;
*pt+=1; 錯誤的語句
cin>>*pt;
pt的聲明並不意味着它指向的值其實是一個常量,而只是說對pt而言,這個值是常量。例如pt指向age 而age不是const 能夠直接經過age變量來修改age的值
*pt=20; 錯誤
age=20 正確
將常規變量的地址賦給常規指針 可行
將const變量的地址賦給指向const的指針 可行 變量和指針都不能修改原值
將const的地址賦給常規指針 不可行 (緣由是常規指針可能會改變指向的const的值 會引起錯誤)
例2:一級指針 一級間接關係 將非const指針賦給const指針是能夠的
int age=39; //age++ 有效
int * pd=&age; //*pd=41 有效
const int * pt=pd; //*pt=42 無效
二級關係
const int **pp2; //二級指針
int * p1;
const int n=13; //常量 變量
pp2=&p1; // 錯誤 將非const地址 賦給const指針 pp2 所以可使用p1來修改const數據,
*pp2=&n; //常量變量的地址賦給一級指針 有效 兩個都是常量
*p1=10; //
若是數據類型自己並非指針,則能夠將const數據或非const數據的地址賦給指向const的指針,但只能將非const數據的地址賦給非const指針。
二級指針不容許賦值
禁止將常量數組的地址賦給很是量指針將意味這不能將數組名做爲參數傳遞給使用很是量形參的函數
const int months[12]={31,28,31,30,31,30,31,31,30,31,30,31};
int sum(int arr[],int n);
int j=sum(months,12);
試圖將const指針賦給非const指針arr 編譯器將禁止這種函數調用
儘量使用const
TIPS:將指針參數聲明爲指向常量數據的指針有兩條理由:
避免無心間修改數據而致使的編程錯誤
使用const使得函數可以處理const和非const實參,不然將只能接受非const數據
int age=39;
const int * pt=&age;
上面的聲明中的const只能防止修改pt指向的值(39),而不能防止修改pt的值。也就是說,能夠將一個新的地址賦值給pt:
int sage=80;
pt=&sage; 不能使用pt來修改sage的值 可是能夠修改pt自己的值 (地址值)
使用const的方式使得沒法修改指針的值
int sloth=3;
const int * ps=&sloth;//指針指向一個整型的常量數值 ----1
int * const finger=&sloth;// 一個常量指針 指向整型-----2
1 不能經過指針 改變指向的常量的值,可是能夠改變指針自己的地址值 const的位置在最前方 能夠不指向sloth
2 不能改變指針自己的地址值,可是能夠經過此指針修改指向變量的值 const的位置在後方 執政指向sloth
總結 finger和*ps都是const;而*finger和ps不是const
double trouble=2.0e30;
const double * const stick=&trouble;
stick 只能指向trouble stick不能用來修改trouble的值, stick和*stick都是const
一般 將指針做爲函數參數來傳遞時,可使用指向const的指針來保護數據。
四 函數和二維數組
爲編寫將二維數組做爲參數的函數 數組名是地址 ,相應的形參就是一個指針 數組名錶明數組組中的第一個元素的地址 &數組名則表明所有數組的地址
int data[3][4]={{1,2,3,4},{9,8,7,6},{2,4,6,8}};
int total=sum(data,3);
data是一個數組名,該數組有3個元素,第一個元素自己是一個數組,有4個int值組成,所以data的類型是指向4個int組成的數組的指針。
data指向二維數組中的第一個元素,表明{1,2,3,4}這個元素的地址
因此正確的原型以下:
int sum(int (*ar2)[4],int size); 可讀性差一些
ar2 接收的就是一個包含4個int型元素的數組的地址值 (指針)
而與上面一樣的原型則爲
int sum(int ar2[][4],int size) 可讀性更好
ar2是指針而不是數組,指針類型指出,由4個int組成的數組,所以,指針類型指定了列數,這就是沒有將列數做爲獨立的函數參數進行傳遞的緣由。
示例:
int a[100][4];
int b[6][4];
int total1=sum(a,100);
int total2=sum(b,6);
示例2:
int sum(int ar2[][4],int size)
{
int total=0;
for(int r=0;r<size;r++) //雙層循環
for(int c=0;c<4;c++)
total+=ar2[r][c];
return total;
}
ar2+r指向編號爲r的元素,所以ar2[r]是編號爲r的元素,因爲該元素自己是一個4個int組成的數組,所以ar2[r]是由4個int組成的數組的名稱。
將下標用於數組名將獲得一個數組元素,所以ar2[r][c]是由4個int組成的數組中的一個元素,是一個int值。必須對指針ar2執行兩次解除引用
最好的方法是兩次方括號 ar2[r][c]
也可使用運算符*兩次
ar2[r][c]==*(*(ar2+r)+c)
ar2 指向二維數組的第一行
ar2+r 指向二維數組中的第r行
*(ar2+r) 指向二維數組中的第r行中的首元素
*(ar2+r)+c 指向二維數組中的第r行中的標籤爲c的元素
*(*(ar2+r)+c) 表明二維數組中的第r行中的標籤爲c的元素的數值
sum()的代碼在聲明參數ar2時,沒有使用const 由於這種技術只能用於指向基本類型的指針,而ar2是指向指針的指針。
五 函數和c風格字符串
c風格字符串 一些列字符組成,以空值字符結尾。
將字符串做爲參數時意味着傳遞的是地址,可是可使用const來禁止對字符串參數進行修改。
1.將c字符串做爲函數的參數
char數組
用引號括起的字符串常量 也稱字符串字面量
被設置爲字符串的地址的char指針
上面的三個都是char *
示例:char gohst[15]="asdfasdf";
char * str="adsfasdf";
int n1=strlen(ghost); ghost表明&ghost[0]
int n2=strlen(str); 指向char的指針
int n3=strlen(「adsfasdf」);括號中字符串的地址
因此 將字符串做爲參數來傳遞,但實際傳遞的是字符串第一個字符的地址 字符串函數原型應將其表示字符串的形參聲明爲char * 類型
c風格字符串與常規char數組的一個重要區別是 字符串有內置的結束字符 但不以空值字符結尾的char數組只是數組,而不是字符串。
長度能夠不用傳遞給函數 由於 函數能夠檢測字符串中的每一個字符 若是遇到空值字符 就是中止。
示例:
#include <iostream>
unsigned int c_in_str(const char * str,char ch)
int main()
{
using namespace std;
char mmm[15]="minimum";
char * wail="ululate";
unsigned int ms=c_in_str(mmm,'m');
unsigned int us=c_in_str(wail,'u');
cout<<ms<<"m characters in"<<mmm<<endl;
cout<<us<<"u characters in"<<wail<<endl;
return 0;
}
unsined int c_in_str(const char * str,char ch)
{
unsigned int count=0;
while(*str)
{
if(*str==ch)
count++;
str++;
}
return count;
}
一樣可行的方式:unsigned int c_in_str(const char str[],char ch)
函數自己演示了處理字符串中字符的標準方式
while(* str)
{
statements
str++;
}
其中*str表示的是第一個字符
當str指向空值字符時,*str等於0 表示空值字符的數字編碼爲0 則循環結束
函數沒法返回一個字符串,可是能夠返回一個字符串的地址,並且效率更高
示例2 strgback.cpp
#include <iostream>
char * buildstr(char c,int n); 原型
int main()
{
using namespace std;
int times;
char ch;
cout<<"enter a character: ";
cin>>ch; \\一個字符
cout<<"enter an integer: ";
cin>>times; \\一個整數
char *ps=buildstr(ch,times); \\返回地址
cout<<ps<<endl;
delete [] ps; \\清除ps指向的內存空間 數組
ps=buildstr('+',20);\\調用函數
cout<<ps<<"-done-"<<ps<<endl;
delete [] ps;
return 0;
}
char * buildstr(char c,int n)
{
char * pstr=new char[n+1];\\申請空間 數組存放 pstr爲指向的指針
pstr[n]='\0'; \\下標爲n的數組值爲空值字符
while(n-->0) \\循環賦值
pstr[n]=c;
return pstr; \\返回字符數組的指針
}
建立包含n個字符的字符串,須要可以存儲n+1個字符的空間,以便可以存儲空值字符。
注意用new 申請的內存空間 必須記住用delete來釋放
6 函數和結構
結構和數組不一樣 結構變量更接近於基本的單值變量,將其數據組合成單個實體或數據對象,該實體被視爲一個總體。
能夠將一個結構賦給另一個結構。也能夠按值傳遞結構,就像普通變量那樣。
結構傳遞副本 函數也可返回結構
結構名不是地址,僅僅是結構的名稱 若是想要獲得結構的地址,必須使用地址運算符&
c++仍是用該運算符來表示引用變量 &
1.傳遞和返回結構
適合結構比較小的狀況
struct travel_time{ \\結構定義
int hours;
int mins;
}
travel_time sum(travel_time t1,travel_time t2); \\ 原型
travel_time day1={5,45};
travel_time day2={4,55};
travel_time trip=sum(day1,day2);\\調用
travel_time sum(travel_time t1,travel_time t2) \\被調用函數的實現
{
travel_time total;
statement;
return total; \\返回一個結構體變量
}
2.處理結構的示例
有關函數和c++結構的大部分知識均可以用於c++類中
示例:
\\strctfun.cpp
#include <iostream>
#include <cmath>
struct ploar \\用距離和角度來表示一個點
{
double distance;
double angle;
}
struct rect \\x和y的座標表示點的位置
{
double x;
double y;
}
ploar rect_to_polar(rect xypos);\\原型聲明
void show_polar(polar dapos);\\原型聲明
int main()
{
using namespace std;
rect rplace;\\結構類型聲明
ploar pplace;\\結構類型聲明
cout<<"enter the x and y values: ";
while(cin>>rplace.x>>rplace.y)\\注意這裏的cin>>rplace.x 返回cin類型 接着調用cin>>rplace.y
{
pplace=rect_to_polar(rplace);\\調用函數
show_polar(pplace);\\調用函數
cout<<"next two numbers (q to quit): ";
}
cout<<"done.\n";
return 0;
} \\end of main
ploar rect_to_polar(rect xypos) \\接受 rect結構體 返回ploar結構體
{
using namesapce std;
ploar answer;
answer.distance=sqrt(xypos.x*xypos.x+xypos.y*xypos.y); \\計算距離
answer.angle=atan2(xypos.y,xypos.x);\\計算角度
return answer;
}
void show_ploar(polar dapos)
{
using namespace std;
const double rad_to_deg=57.29577951;
cout<<"distance= "<<dapos.distance;
cout<<",angle="<<dapos.angle * rad_to_deg;\\角度轉換
cout<<"* degrees\n";
}
2.傳遞結構的地址
傳遞結構的地址而不是整個結構以節省時間和空間,使用指向結構的指針。
調用函數,將結構的地址 &pplace而不是結構自己pplace傳遞給它;
將形參聲明爲指向polar的指針,即ploar* 類型。因爲函數不該該修改結構,所以使用了const修飾符;
形參是指針而不是接哦股,所以應間接成員運算符-> 而不是成員運算符 句點。
修改後的代碼以下 show_polar(const ploar *pda)和rect_to_polar()
\\strctfun.cpp
#include <iostream>
#include <cmath>
struct ploar \\用距離和角度來表示一個點
{
double distance;
double angle;
}
struct rect \\x和y的座標表示點的位置
{
double x;
double y;
}
\\ploar rect_to_polar(rect xypos);\\原型聲明
void rect_to_polar(const rect * pxy,ploar * pda);\\原型聲明
void show_polar(const polar * pda);\\原型聲明
int main( )
{
using namespace std;
rect rplace;\\結構類型聲明
ploar pplace;\\結構類型聲明
cout<<"enter the x and y values: ";
while(cin>>rplace.x>>rplace.y)\\注意這裏的cin>>rplace.x 返回cin類型 接着調用cin>>rplace.y
{
\\pplace=rect_to_polar(rplace);\\調用函數
rect_to_polar(&rplace,&pplace);\\傳遞地址
show_polar(&pplace);\\調用函數 \\傳遞地址
cout<<"next two numbers (q to quit): ";
}
cout<<"done.\n";
return 0;
} \\end of main
ploar rect_to_polar(const rect * pxy,ploar * pda) \\接受 rect結構體 返回ploar結構體
{
using namesapce std;
pda->distance=sqrt(pxy->x*pxy->x+pxy->y*pxy->y); \\計算距離
pda->angle=atan2(pxy->y,pxy->x);\\計算角度
}
void show_ploar(const polar * pda)
{
using namespace std;
const double rad_to_deg=57.29577951;
cout<<"distance= "<<pda->distance;
cout<<",angle="<<pda->angle * rad_to_deg;\\角度轉換
cout<<"* degrees\n";
}
上例使用了指針 函數可以對原始的機構進行操做;而上上例使用的是結構副本。
7.函數和string對象
c風格字符串和string對象的用途相似,但與數組相比,string對象與結構更類似。
結構能夠賦值給結構,對象能夠賦值給對象。
若是須要多個字符串,能夠聲明一個string對象的數組,而不是二維char數組,操做會更加靈活。
//topfive.cpp
#include <iostream>
#include <string>
using namespace std;
const int SIZE=5;
void display(const string sa[],int n);\\原型
int main()
{
string list[SIZE]; \\string對象數組
cout<<"enter your "<<SIZE<<" favorite astronomical sights: \n ";
for(int i=0;i<SIZE;i++)
{
cout<<i+1<<": ";
getline(cin,list[i]);\\初始化數組中的元素
}
cout<<" your list: \n ";
display(list,SIZE); \\傳遞過去指向string對象數組的指針 (即地址值 =數組名)
return 0;
}
void display(const string sa[],int n)
{
for(int i=0;i<n;i++)
cout<<i+1<<" : "<<sa[i]<<endl;
}
8.函數與array對象
c++中 類對象基於結構
因此:能夠按值將對像傳遞給函數, 函數處理的是原始對象的副本。
能夠傳遞對象的指針 函數內部直接操做原始的對象 經過指針
使用array對象來存儲一年四個季度的開支:
std::array<double,4> expenses; 須要包含頭文件array,而名稱array位於名稱空間std中。
函數show(expenses) 不能修改expenses
函數fill(&expenses) 能夠修改expenses 傳遞進來的是指針
則函數的原型爲:
void show(std::array<double,4> da);
void fill(std::array<double,4> * pa); \\pa指向array對象的指針
能夠用符號常量來替換4
const int seasons=4;
const std::array<std::string,seasons> snames={"spring","summer","fall","winter"};
模板array並不是只能存儲基本數據類型,還能夠存儲類對象
//arrobj.cpp
#include <iostream>
#include <array>
#include <string>
const int seasons=4;
const std::array<std::string,seasons> snames={"spring","summer","fall","winter"}; //數組的聲明
void fill(std::array<double,seasons> * pa);//原型定義 接收一個array的對象的指針
void show(std::array<double,seasons> da);//原型定義 接收一個array的對象
int main()
{
std::array<double,seasons> expenses; //定義
fill(&expenses);//填充 指針指向的array對象定義的double數組
show(expenses);//傳值 而且顯示 數組中的內容
return 0;
}
void fill(std::array<double,seasons> * pa) //接收指向數組的指針
{
using namespace std;
for(int i=0;i<seasons;i++)
{
cout<<"enter "<<snames[i]<<" expenses: ";
cin>>(*pa)[i]; //填充 數組元素
}
}
void show(std::array<double,seasons> da)
{
using namespace std;
double total=0.0;
cout<<" \n EXPENSES \n ";
for(int i=0;i<seasons;i++)
{
cout<<snames[i]<<" :$ "<<da[i]<<endl;//輸出 個元素的值
total+=da[i];//相加各個元素的值
}
cout<<"total expenses: $ " <<total<<endl;
}
fill和show兩個函數都有缺點。函數show()存在的問題是,expenses存儲了四個double值,而建立一個新對象並將expenses的值賦值到其中的效率過低。
7.9遞歸
1.包含一個遞歸調用的遞歸
若是遞歸函數調用本身,則被調用的函數也將調用本身,這將無限循環下去,因此必須有終止調用鏈的內容。
void recurs(argumentlist)
{
statements1
if(test)
recurs(arguments)
statements2
}
示例:
recur.cpp
#include <iostream>
void countdown(int n)
int main()
{
countdown(4);
return 0;
}
void countdown(int n)
{
using namespace std;
cout<<"counting down ..."<<endl;
if(n>0)
countdown(n-1);
cout<<n<<":kaboom\n";
}
輸出:
counting down ...4
counting down ...3
......
counting down ...0
0:kaboom!
1:kaboom!
......
4:kaboom!
7.9.2包含多個遞歸調用的遞歸
將一項工做不斷分爲兩項或多項較小的工做時,遞歸會很是有用。
示例:繪製標尺 分而治之的策略
標出兩端,找到重點並將其標出 而後將一樣的操做用於標尺的左半部分和右半部分
//ruler.cpp
#include <iostream>
const int len=66;
const int divs=6;
void subdivide(char ar[],int low,int high,int level);//prototype
int main()
{
char ruler[len];//數組
int i;
for(i=1;i<len-2;i++)
ruler[i]=' ';//初始化數組中的值均爲空格
ruler[len-1]='\0';//數組中最後一個元素的值爲空值
int max=len-2;//數組下標範圍設置
int min=0;
ruler[min]=ruler[max]='|';//初始化數組首尾兩個元素的值爲‘|’
std::cout<<ruler<<std::endl;//輸出數組各項的值
for(i=1;i<=divs;i++)//6次循環 分別設置數組中的值 利用遞歸調用
{
subdivide(ruler,min,max,i);//當level=0時會從調用函數中返回
std::cout<<ruler<<std::endl;//輸出數組中個元素的值 分次
for(int j=1;j<len-2;j++)
ruler[j]=' ';
}
return 0;
}
void subdivide(char ar[],int low,int high,int level)//遞歸函數設置數組值
{
if(level==0)
return;
int mid=(high+low)/2;
ar[mid]='|';
subdivide(ar,low,mid,level-1);//左邊的數組 注意level的值是一直變化的
subdivide(ar,mid,high,level-1);//右邊的數組
}
使用level來控制遞歸層,函數調用自身時,將把level減1,當level爲0時,該函數將再也不調用本身。 最初的中點被用做一次調用的右端點和另外一次調用的左端點。
調用次數呈幾何級增加,調用一次致使兩個調用而後致使4個,而後8個 最後64個調用。
7.10函數指針
函數地址是存儲機器語言代碼的內存的開始地址
A.基礎知識
一個函數以另外一個函數做爲參數 此時就會須要函數的地址
獲取函數的地址
聲明一個函數指針
使用函數指針來調用該函數
1.獲取函數的地址
函數名即爲函數的地址
有think()函數
例如:process(think) 傳遞函數的地址 做爲參數
process(think()) 傳遞函數think()的返回值作爲參數
2.聲明函數指針
聲明指針必須指定指針的類型。指向函數,也必須指定指針指向的函數的類型
聲明須要指定函數的返回類型以及函數的參數列表
首先聲明被傳遞的函數的原型
double pam(int);//prototype
double (*pf)(int); pf爲指向函數的指針
提示:一般 用*pf替換原來函數原型中的函數名 就會獲得函數的指針
上例中爲*pf替換了pam
注意運算符號的優先級:
double (*pf)(int) 指向函數的指針
double *pf(int ) pf是一個返回指針的函數
正確的聲明以後, 就能夠將函數的地址賦給指針
pf=pam 注意聲明中指針與原來的函數原型返回值和參數列表必須徹底一致
做爲參數傳遞
void estimate(int lines,double(*pf)(int)); 函數原型
estimate(50,pam); 調用
3.使用指針來調用函數
由於*pf等同於pam 因此直接使用*pf做爲函數名便可
double x=pam(4); 用原函數調用
double y=(*pf)(5); 用指針調用
double y=pf(5); 此種方式也能夠 僅僅在c++11 中
B 函數指針示例
//7.18 fun_prt.cpp
#include <iostream>
double betsy(int);//prototype
double pam(int);
void estimate(int lines,double (*pf)(int));
int main()
{
using namespace std;
int code;
cout<<"how many lines of code do you need?";
cin>>code;
cout<<"here's betsy's estimate:\n";
estimate(code,betsy);// 調用betsy函數
cout<<"here's pam's estimate:\n";
estimate(code,pam);//調用pam函數
return 0;
}
double betsy(int lns)
{
return 0.05*lns;
}
double pam(int lns)
{
return 0.03*lns+0.0004*lns*lns;
}
void estimate(int lines,double(*pf)(int))
{
using namespace std;
cout<<lines<<"lines will take";
cout<<(*pf)(lines)<<"hour(s)\n";
}
C 深刻探討函數指針
一下的聲明徹底相同
const double * f1(const double ar[],int n);//以數組的方式聲明 ar[] 與指針*ar 做爲參數時同樣都是指針
const double *f2(const double [],int );省略標識符
const double *f4(const double *ar,int n);
const double *f3(const double *,int);指針形式
假設要聲明一個指針,指向函數,若是指針名爲pa 則只需將目標函數原型中的函數名替換爲(*pa)便可
const double *(*pa)(const double *,int); pa=f3;
const double *(*pa)(const double *,int)=f3 同時初始化 等同與上一句
auto pa2=f1;使用c++11中的auto 自動類型推斷功能 簡化聲明
示例:
cout<<(*pa)(av,3)<<":"<<*(*pa)(av,3)<<endl;
cout<<pa(av,3)<<":"<<*pa(av,3)<<endl;
f3返回值的類型爲const double * 因此前面的輸出都爲函數的返回值 指向const double的指針 即地址值
然後面的輸出均加上了* 解引用運算符 因此實際輸出爲指針指向的double值
D 函數指針數組
---恢復內容結束---
c++ primer plus 第6版 部分二 5- 章
第五章
計算機除了存儲外 還能夠對數據進行分析、合併、重組、抽取、修改、推斷、合成、以及其餘操做
1.for循環的組成部分
a 設置初始值
b 執行測試,看循環時候應當繼續進行
c 執行循環操做
d 更新用於測試的值
只要測試表達式爲true 循環體就會執行
for (initialization; test-expression; update-expression)
body
test-expression決定循環體是否被執行,一般這個表達式是關係表達式,c++會將此結果強制轉換爲bool類型。值0的表達式將被轉換爲bool值false,致使循環結束。
for 後面跟着一對括號 它是一個c++關鍵字 所以編譯器不會將for視爲一個函數
1. 表達式和語句
任何值或任何有效的值和運算符的組合都是表達式。c++中每一個表達式都有值。一般值是很明顯的。
c++將賦值表達式的值定義爲左側成員的值,所以這個表達式的值爲20.因爲賦值表達式有值,能夠編寫
maids=(cooks=4)+3; 值爲7
賦值表達式從右向左結合
x<y 這樣的關係表達式被斷定爲true或者false
int x;
cout<<(x=100)<<endl; //x=100
cout<<(x<3)<<endl; //0
cout<<(x>3)<<endl; //1
cout.setf(ios_base::boolalpha);//老式的c++實現使用ios:boolalpha 來做爲setf()的參數
cout<<(x<3)<<endl; //false
cout在現實bool值以前將他們轉換爲int 可是cout.setf(ios::boolalpha) 函數調用設置了一個標記,該標記命令cout顯示true和false 而不是1和0;
斷定表達式的值 改變了內存中數據的值時,表達式有反作用 side effect。
例如x++ 斷定賦值表達式 改變了x的值 因此有反作用
有例如x+100 斷定賦值表達式 計算出一個新值 未改變x的值 因此沒有反作用
表達式到語句的轉換很是容易 只須要加上分號便可。
2.非表達式和語句
返回語句 聲明語句 和for語句都不知足「語句=表達式+分號」這種模式
int a; 這是一條語句 可是int a並非表達式 由於它沒有值
因此這些代碼是非法的:
eggs=int a *1000;
cin>>int a;
不能把for循環賦值給變量
3.修改規則
c++ 在c循環的基礎上添加了一些特性 能夠對for循環句法作一些調整
for(expression;expression;expression)
statement;
示例:
for(int i=0;i<5;i++) int i=0 叫作生命語句表達式 (不帶分號的聲明) 此種只能出如今for語句中
其中 int i=0 爲聲明 不是表達式 這因爲上面的語法向矛盾
因此這種調整已經被取消
修改以後的語法:
for (for-init-statement condition;expression)
statement
感受奇怪,這裏只用了一個分號,可是這是容許的由於for-init=statement被視爲一條語句,而語句有本身的分號。對於for-init-statement來講,它既能夠是表達式語句,也能夠是聲明。
語句自己有本身的分號,這種句法規則用語句替換了後面跟分號的表達式。在for循環初始化部分中聲明和初始化變量。
在for-init-statement中聲明變量還有其實用的一面。這種變量只存在於for語句中,當程序離開循環後,這種變量將消失。
for(int i=0;i<5;i++)
較老的c++實現遵循之前的規則,對於前面的循環,將把i視爲在循環以前聲明 因此在循環結束後,i變量仍然存在。
4.回到for循環
const int arsize=16;// 定義整型的常量 在下方程序中使用 數組長度的定義
for(int i=2;i<arsize;i++) //i<arsize 下標從0到arsize-1 因此數組索引應該在arsize-1的位置中止。也可使用i<=arsize-1 可是沒有前面的表達式好。
5.修改步長
以前的循環計數都是加1或者減1 能夠經過修改更新表達式來修改步長。i=i+by
int by;
cin>>by;
for(int i=0;i<100;i=i+by)
注意檢測不等號一般要比檢測相等要好。上例中若是將i<100改成i==100 則不可行 由於i的取值不會爲100
using聲明和using編譯指令的區別。
6.使用for循環訪問字符串
示例:
#include <iostream>
#include <string>
int main(){
using namespace std;
cout<<"enter a word"<<endl;
string word; //定義對象
cin>>word;
for(int i=word.size()-1;i>=0;i--) //word對象的字符串長度 size成員函數
cout<<word[i]; //輸出各個字符
cout<<"\n----Bye.\n"<<endl;
return 0;
}
若是所用的實現沒有添加新的頭文件,則必須使用string.h 而不是cstring
7.遞增運算符++ 遞減運算符--
都是講操做數相加或者相減 他們有兩種不一樣的形式 一種爲前綴 另外一種爲後綴 ++x x++
可是他們對操做數的影響的時間是不一樣的
int a=20 b=20;
cout<<"a++="<<a++<<"++b= "<<++b<<endl;
cout<<"a="<<a<<"b= "<<b<<endl;
a++爲使用a的當前值計算表達式 而後將a的值加1 使用後修改
++b是先將b的值加1,而後使用新的值來計算表達式 修改後再使用
8.反作用 和順序點
side effect 反作用
指的是在計算表達式時對某些東西進行了修改 例如存儲在變量中的值
順序點是程序執行過程當中的一個點。c++中 語句中的分號就是一個順序點 這就意味着程序處理下一條語句以前,賦值運算符、遞增運算符和遞減運算符執行的全部修改都必須完成。任何完整的表達式末尾都是一個順序點。
表達式的定義:不是另一個表達式的子表達式
順序點有助於闡明後綴遞增什麼時候進行
while(guest++<10)
cout<<guests<<endl;
相似於只有測試表達式的for循環。可能會被認爲是使用值而後再遞增即cout中先使用guests的值 而後再將其值加1。
可是guests++<10 是一個完整的表達式,由於它是while循環的一個測試條件。因此該表達式的末尾是一個順序點。因此c++確保反作用(將guests+1) 在程序進入cout以前完成,然而使用後綴的格式,能夠確保將guests同10進行比較後再將其值加1。
y=(4+x++)+(6+x++);
表達式4+x++不是一個完整的表達式,所以,c++不保證x的值在計算子表達式4+x++後馬上增長1;在這個例子中 紅色部分是一個完整的表達式 其中的分號標示了順序點,所以c++只保證程序執行到下一條語句以前,x的值將被遞增兩次。c++沒有規定是在計算每一個子表達式以後將x的值遞增,仍是在整個表達式計算完畢後纔將x的值遞增,有鑑於此,應該避免使用這樣的表達式。
c++11文檔中,再也不使用術語「順序點」了,由於這個概念難以用於討論多線程執行。相反,使用了術語「順序」,它表示有些事件在其餘事件前發生。這種描述方法並不是要改變規則,而旨在更清晰第描述多線程編程。
10.前綴格式和後綴格式
顯然,若是變量被用於某些目的(如用做函數參數或給變量賦值),使用前綴格式和後綴格式的結果將是不一樣的。然而,若是遞增表達式的值沒有被使用,狀況會變得不一樣例如
x++;
++x;
for(n=lim;n>0;--n)
for(n=lim;n>0;n--)
從邏輯上說,在上述兩種情形下,使用前綴格式和後綴格式沒有任何區別。表達式的值未被使用,所以只存在反作用。
在上面的例子中使用這些運算符的表達式爲完整表達式,所以將x+1和n-1的反作用將在程序進入下一步以前完成,因此前綴與後綴格式的最終效果相同。
前綴與後綴的區別2:它們的執行速度有略微的差異 雖然對程序的行爲沒有影響
c++容許您針對類定義這些運算符,在這種狀況下,用戶這樣定義前綴函數:將值加1而後返回結果。
後綴版本:首先複製一個副本,將其加1 而後將複製的副本返回
顯然前綴的效率要比後綴版本高。
總之,對於內置類型,採用哪一種格式不會有差異;可是對於用戶定義的類型,若是有用戶定義的遞增和遞減運算符,則前綴格式的效率更高。
11.遞增/遞減運算符和指針
能夠將遞增運算符用於指針和基本變量。將遞增運算符用於指針時,將把指針的值增長其指向的數據類型佔用的字節數,這種規則適用於對指針遞增和遞減。
double arr[5]={21.1,32.8,23.4,45.2,37.4}
double * pt=arr; //pt points to arr[0],i.e. to 21.1
++pt;//指向arr[1] i.e. to 32.8
也能夠結合使用這些運算符來修改指針指向的值。將*和++同時用於指針時提出了這樣的問題:
將什麼解除引用,將什麼遞增?
取決於運算符的位置和優先級。
1.前綴遞增、後綴遞減和解除引用運算符的優先級相同,以從右到左的方式進行結合。
2.後綴遞增和後綴遞減的優先級相同,可是比前綴運算符的優先級高,這兩個運算符以從左到右的方式進行結合。
例如:*++pt的含義 先將++應用於pt 而後再將*應用於遞增後的pt pt的指向已經變化
++*pt 先取得pt指向的值,而後再將這個值加1 pt的指向沒有變化
(*pt)++ pt指針先解除引用 獲得值 而後再加1 原來爲32.8 以後的值爲32.8+1=33.8 pt的指向未變
x=*pt++; 後綴的運算符++的優先級更高,意味着將運算符用於pt 而不是*pt,所以對指針遞增。而後後綴運算符意味着將對原來的地址&arr[2],而不是遞增後的新地址解除引用,所以*pt++的值爲arr[2],
#include <iostream>
int main(){
using namespace std;
int word[10]={1,2,3,4,5,6,7,8,9,10};//定義數組
int *pt= word; //定義指針指向的數組
int x;
for(int i=0;i<10;i++)
cout<<word[i]<<" , ";
cout<<endl;
cout<<&word<<endl;//輸出數組首地址
cout<<pt<<endl; //指針的方式輸出數組首地址
for(int i=0;i<10;i++)
{
cout<<"dizhi= "<<&(word[i])<<" "; //輸出數組的每一個元素的地址
cout<<"*pt++= "<<*pt++<<" "; //輸出指針形式的每一個元素
cout<<"&pt=="<<pt<<endl; //地址的變化 關鍵是查看這兩行
}
return 0;
}
12.組合賦值運算符
i=i+by 更新循環計數
i+=by
+=運算符將兩個操做數相加,並將結果賦值給左邊的操做數。這意味着左邊的操做數必須可以被賦值,如變量、數組元素、結構成員或者經過對指針解除引用來標識的數據:
示例:
k+=3; 左邊能夠被賦值 能夠k=k+3
int * pa= new int[10]; pa指向數組int
pa[4]=12;
pa+=2; pa指向 pa[4]
34+=10; 非法 34不是一個變量 不能夠被賦值
13.複合語句 (語句塊)
for循環中的循環體 就是複合語句 由大括號括進
外部語句塊中定義的變量在內部語句塊中也定義的變量 狀況以下:在聲明位置到內部語句塊結束的範圍以內,新變量將隱藏舊變量,而後舊變量再次可見以下
#inlcude <iostream>
{ using namespace std;
int x=20; //原始的變量
{ //代碼塊開始
cout<<x<<endl; //使用原始的變量 20
int x=100; //定義新的變量
cout<<x<<endl; //使用新的變量 100
}
cout<<x<<endl; //使用舊的變量 20
return 0;
}
14.其餘語法技巧----逗號運算符
語句塊容許把兩條或更多條語句放到按c++句法只能放一條語句的地方。
逗號運算符對錶達式完成一樣的任務,容許將兩個表達式放到c++句法只容許放一個表達式的地方。
例如循環控制部分的更新部分 只容許這裏包含一個表達式,可是有i和j兩個變量,想同時變化 此時就須要逗號運算符將兩個表達式合併成一個
++j,--i 這兩個表達式合併成一個
逗號並不老是逗號運算符 例如 聲明中的逗號將變量列表中相鄰的名稱分開:
int i,j;
for(j=0,i=word.size()-1;j<i;--i,++j)
逗號運算符另外的特性:確保先進算第一個表達式,而後計算第二個表達式(逗號運算符是一個順序點)
i=20,j=2*i
另外逗號表達式的值爲上面第二部分的值 上面爲40,由於j=2*i的值爲40. 逗號的運算符的優先級是最低的。
data=17,24 被解釋爲(data=17),24 結果爲17 24不起做用
data=(17,24) 結果爲24 由於括號的優先級最高 從右向左結合 括號中的值爲24(逗號右側的表達式的值) 因此data的值最後爲24
15 關係表達式
關係運算符的優先級比算數運算符的低。 注意將bool的值提高爲int後,3>y要麼是1 或者是0 因此表達式有效
for(x=1;y!=x;++x) 有效
for(cin>>x;x==0;cin>>x)
(x+3)>(y-2) 有效
x+(3>y)-2 有效
16賦值 比較和可能犯的錯誤
== 和= 是有區別的 前者是判斷 關係表達式 後者是賦值表達式
若是用後者則當在for循環中 測試部分將是一個複製表達式 ,而不是一個關係表達式,此時雖然代碼仍然有效,可是賦值表達式 爲非0 則結果爲true 。
示例:測試數組中的分數
for(i=0;scores[i]==20;i++) 測試成績是否爲20 原來的數組值不變
for(i=0;socres[i]=20;i++) 測試成績是否爲20 可是原來的數組值已經都賦值爲20了
1.賦值表達式 爲非0 則 始終爲true 2.實際上修改的數組的元素的數據 3. 表達式一直未true 因此程序再到達數組結尾後,仍然在不斷地修改數據
可是代碼在語法上是正確的,所以編譯器不會將其視爲錯誤
對於c++類,應該設計一種保護數組類型來防止越界的錯誤。循環須要測試數組的值和索引的值。
17.c風格字符串的比較
數組名錶明數組首地址
引號括起來的字符串常量也是其地址
因此word=="mate" 並非判斷兩個字符串是否相同,而是查看它們是否存儲在相同的地址上 前者爲數組 後者爲字符串常量 ,因此兩個地址必定是不一樣的,即便word數組中的數據是mate字符串。
因爲c++將c風格的字符串視爲地址,因此使用關係運算符來比較它們,將不會獲得滿意的結果。
相反應該使用c風格字符串庫中的strcmp()函數來比較。 此函數接收兩個字符串地址做爲參數,參數可使指針、字符串常量或者字符數組名,若是兩個字符串相同,則函數返回0;若是第一個字符串的字母順序排在第二個字符串以前,則strcmp()函數將會返回一個負值,相反則返回一個正值。
還有按照「系統排列順序」比「字母順序」更加準確,此時意味着字符是針具字符的系統編碼來進行比較的ascii碼。全部的大寫字母的編碼都比小寫字母要小,因此按順序排列,大寫位於小寫以前。
另外一個方面 c字符串經過結尾的空值字符定義,而不是數組的長度
因此char big[80]="daffy" 與 char little[10]="daffy" 字符串是相同的
不能用關係運算符來比較字符串,可是能夠用來比較字符,由於字符其實是整型 因此能夠用下面的代碼顯示字母表中的字符
for(ch='a';ch<='z';ch++)
cout<<ch;
示例:
#include <iostream>
#include <cstring>
int main(){
using namespace std;
char word[5]="?ate"; //定義字符數組
for(char ch='a';strcmp(word,"mate");ch++)//判斷字符數組中的首元素是否爲m 爲循環的條件 若是不是則返回負值 非0 因此爲真,直到 ch=l時 ch++=m strcmp返回0 =false 結束循環
{
cout<<word<<endl;
word[0]=ch;//修改字符數組的首元素爲ch
}
cout<<"after loop ends,word is "<<word<<endl;
return 0;
}
檢測相等或排列順序
可使用strcmp()來測試c風格字符串是否相等 排列順序,若是str1和str2相等,則下面的表達式爲true
strcmp(s1,s2)==0 s1 s2相等 函數返回0 0==0 則爲true
strcmp(s1,s2)!=0 s1 s2相等 .. 0!=0 、爲false
strcmp(s1,s2) s1 s2相等返回0 爲false
strcmp(s1,s2)<0 s1在s2的前面爲true s1-s2<0
因此根據測試的條件 strcmp()能夠扮演== != <和>運算符的角色
char其實是整型 修改char 類型的操做其實是修改存儲在變量中的整數的編碼 另,使用數組索引可以使修改字符串中的字符更爲簡單
18.比較string類字符串
使用string類的字符串 比使用c風格字符串要簡單 類設計可使用關係運算符進行比較。
緣由是由於類函數重載(從新定義)了這些運算符
示例:
#include <iostream>
#include <string> //頭文件不一樣
int main(){
using namespace std;
string word="?ate"; //定義字符串對象
for(char ch='a';word!="mate";ch++)//循環
{
cout<<word<<endl;
word[0]=ch;//修改字符數組的首元素爲ch
}
cout<<"after loop ends,word is "<<word<<endl;
return 0;
}
程序說明:
word!="mate" 使用了關係運算符 左邊是一個string類的對象,右邊是一個c風格的字符串
string類重載運算符!=的方式可使用的條件:
1.至少有一個操做數爲string對象,另外一個操做數能夠是string對象,也能夠是c風格字符串
2.此循環不是計數循環 並不對語句塊執行指定的次數,相反 它根據實際的狀況來肯定是否中止。 此種狀況一般使用while循環
二 while循環
while循環是沒有初始化和更新部分的for循環 它只有測試條件和循環體
while (test-condition)
body
首先程序計算圓括號內測試條件表達式 若是表達式爲true 則執行body 若是表達式維false 則不執行
body代碼中必須有完成某種影響測試條件表達式的操做。 例如 循環能夠將測試條件中的變量加1或者從鍵盤輸入讀取一個新值。
while循環也是一種入口條件循環。所以 測試條件一開始便爲false 則程序將不會執行循環體。
while(name[i]!='\0')
測試數組中特定的字符是否是空值字符。
1.for與while
本質上二者是相同的
for(init-expression;test-expression;update-expression)
{ statement(s) }
能夠寫成以下的形式
init-expression;
while(test-expression)
{
statement(s)
update-expression;
}
一樣的
while(test-expression) body
能夠寫成
for(;test-expression;)
body
for循環須要3個表達式 技術上說,須要1條後面跟兩個表達式的語句。不過它們能夠是空表達式,只有兩個分好是必須的。
另外for循環中 省略測試表達式時 測試結果將是true 所以 for(;;) body 會一直執行下去
差異:
1.for 沒有測試條件 則條件爲true
2.for中可使用初始化語句聲明一個局部變量 可是while不能這樣作
3.若是循環體中包含continue語句 狀況將會有所不一樣。
一般若是將初始值,終止值和更新計數器的、都放在同一個地方,那麼要使用for循環格式
當在沒法預先知道循環將執行的次數時,程序員常使用while循環
當使用循環時 要注意下面的幾條原則
1.指定循環終止的條件
2.在首次測試以前初始化條件
3.在條件被再次測試以前更新條件
錯誤的標點符號
for循環和while循環都由用括號括起的表達式和後面的循環體(包含一條語句)組成。
語句塊 由大括號括起 分號結束語句
2.等待一段時間,編寫延時循環
clock()函數 返回程序開始執行後所用的系統時間
#include <iostream>
#include <ctime>//describes clock() function, clock_t loop
int main()
{
using namespace std;
cout<<"enter the delay time,in seconds: ";
float secs;
cin>>secs;
clock_t delay=secs * CLOCKS_PER_SEC;//轉化成clock ticks的形式 始終滴答形式
cout<<"starting\a\n";
clock_t start=clock();
while(clock()-start<delay)//等待時間 直到爲假時跳出循環
; // 空操做 空語句
cout<<"done \a\n";
return 0;
}
頭文件ctime (time.h) 提供了這些問題的解決方案。首先,定義了一個符號常量--CLOCKS_PER_SEC 該常量等於每秒鐘包含的系統時間單位數。所以,將系統時間除以這個值,能夠獲得秒數。或者將秒數乘以CLOCK_PER_SEC 能夠獲得以系統時間單位爲單位的時間。其次,ctime將clock_t做爲clock()返回類型的別名 ,這意味着能夠將變量聲明爲clock_t類型 編譯器將把它轉換爲long 、unxigned int 或者適合系統的其餘類型。
類型的別名
c++爲類型創建別名的方式有兩種。
一種是使用預處理器:
#define BYTE char
這樣預處理器將在編譯程序時用char 替換全部的BYTE,從而使BYTE稱爲char的別名
第二種是使用c++和c的關鍵字typedef來建立別名。例如要講byte做爲char的別名,
typedef char byte;
通用格式:typedef typeName aliasName; aliasName爲別名
兩種比較
示例:
#define FLOAT_POINTER float *
FLOAT_POINTER pa,pb;
預處理器置換將該聲明轉換爲以下的形式
float *pa,pb; 此時 pa爲指針 而pb不是指針 只是float類型
typedef 方法不會有這樣的問題 它可以處理更加複雜的類型別名 最佳選擇,有時候是惟一的選擇
不會建立新的類型 僅僅是這種類型的另外一個名稱
三 do-while循環
前面兩種是入口條件的循環 ,而這種事出口條件的循環 意味着這種循環將首先執行循環體,而後再斷定測試表達式,決定是否應該繼續執行循環。若是條件爲false 則循環終止。不然進入新的執行和測試。 此時循環體至少要循環一次。
do
body
while(test-expression);
一般狀況下 入口條件循環要比出口條件循環好 由於入口條件循環在循環開始以前對條件進行檢查,可是有時候須要do while更加合理 例如請求用戶輸入時,程序必須先得到輸入,而後對它進行測試
//dowhile.cpp
#include <iostream>
int main(){
using namespace std;
int n;
cout<<"enter number in the range 1-10 to find"<<endl;
cout<<"my favorite number\n";
do{
cin>>n;//接收鍵盤的輸入
}
while (n!=7);//當 輸入的值不等於7時 判別式爲真值 繼續循環 等於7就退出循環 進行下面的語句的執行
cout<<"yes,7 is my favorite.\n";
return 0;
}
奇特的for循環
int i=0;
for(;;)
{
i++;
cout<<i<<endl;
if(30>=i) break;
}
或者另一種變體
int i=0;
for(;;i++)
{
if(30>=i) break;
//do something
}
上述的代碼基於這樣的一個事實:for循環中的空測試條件被視爲true。 不利於閱讀 在do while中更好的表達它們的意思
int i=0;
do{
i++;
//do something
}while(30>=i);
第二個例子的轉化
while(i<30)
{
//do something;
i++;
}
4.基於範圍的for循環(c++11)
c++11新增的一種循環
基於範圍 range-based的for循環:對數組(或容器類,如vector和array)的每一個元素執行相同的操做
double prices[5]={4.99,10.99,6.87,7.99,8.49};
for(double x:prices)
cout<<x<<std::endl;
其中x最初表示數組prices的第一個元素。顯示第一個元素後,不斷執行循環,而x依次表示數組的其餘元素。所以,上述代碼顯示所有5個元素,每一個元素佔據一行。總之,該循環顯示數組中的每一個值,要修改數組的元素,須要使用不一樣的循環變量的語法:
for(double &x:prices)
x=x*0.80;
示例1
#include <iostream>
int main(){
using namespace std;
double prices[5]={4.99,10.99,6.87,7.99,8.49};
for(double x:prices) //x最初表示首元素 以後x依次表示數組的其餘元素
cout<<x<<endl;
return 0;
}
將上面的代碼中的循環更改成for(double &x:prices)
x=x*0.80; //價格變爲8折
&代表x是一個引用變量(而不是取地址),這種聲明讓接下來的代碼可以修改數組的內容,而第一種語法不能。
也可使用基於範圍的for循環
for(int x:{3,5,2,8,6})
cout<<x<<" ";
5.循環和文本輸入
系統中最多見的 最重要的任務:逐字符地讀取來自文件或鍵盤的文本。
例如想編寫一個可以計算輸入中的字符數,行數和字數的程序,c++與c在i/o工具不盡相同 cin對象支持3中不一樣模式的單字符輸入,其用戶接口各部相同。
1.使用原始的cin進行輸入
若是程序要使用循環來讀取來自鍵盤的文本輸入,則必需要知道什麼時候中止讀取。一種方法就是選擇某個字符 做爲哨兵字符 將其做爲中止標記。
示例:
//testin1.cpp
#include <iostream>
int main(){
using namespace std;
char ch;
int count=0;
cout<<"enter characters;enter # to quit:\n";
cin>>ch;
while(ch!='#')//以#字符爲終止符 若是輸入中不是# 就繼續執行循環
{
cout<<ch;
++count;
cin>>ch;//讀取下一個字符 很重要 若是沒有此條,就會重複處理第一個字符
}
cout<<endl<<count<<"characters read\n";
return 0;
}
程序說明
此循環遵循了前面的循環原則,結束條件爲最後讀取的字符爲#,在循環以前讀取一個字符進行初始化,而經過循環體結尾讀取下一個字符進行更新。
在輸入時 能夠輸入空格和回車 可是在輸出時,cin會將輸入的字符中的空格和換行符忽略。因此輸入的空格或者回車符沒有被回顯,也沒有被包括在計數內。
更加複雜的是,發送給cin的輸入被緩衝,這意味着只有在用戶按下回車鍵後,他輸入的內容纔會被髮送給程序。這就是在運行該程序時,能夠在#後面輸入字符的緣由。按下回車鍵後,這個字符序列講被髮送給程序,可是程序在遇到#字符後將結束對輸入的處理。
2.使用cinget(char)進行補救
一般,逐個字符讀取輸入的程序須要檢查每一個字符,包括空格、製表符和換行符。cin所屬的istream類(在iostream中定義)中包含一個可以知足這種要求的成員函數。
cin.get(ch)讀取輸入中的下一個字符(即便它是空格),並將其賦值給變量ch。使用這個函數調用替換cin>>ch,能夠修補上面程序的問題
//testin1.cpp
#include <iostream>
int main(){
using namespace std;
char ch;
int count=0;
cout<<"enter characters;enter # to quit:\n";
cin.get(ch); //會顯示空格 製表符等其餘的字符
while(ch!='#')
{
cout<<ch;
++count;
cin.get(ch);
}
cout<<endl<<count<<"characters read\n";
return 0;
}
程序說明:可能的誤解:c語言中 cin.get(ch)調用將一個值放在ch變量中,這意味着將修改該變量的值
c中要修改變量的值,必須將變量的地址傳遞給函數。
在上例中 cin.get() 傳遞的是ch 而不是&ch 在c中此代碼無效,可是在c++中有效,只要函數將參數聲明爲引用便可。
引用在c++中對比c新增的一種類型。頭文件iostream 將cin.get(ch)的參數聲明爲引用類型,所以該函數能夠修改其參數的值。
3.使用哪個cin.get()
char name[arsize];
...
cout<<"enter your name:\n";
cin.get(name,arsize).get();//至關於兩個函數的調用。 cin.get(name,arsize);cin.get();
cin.get 的一個版本接收兩個參數 :數組名 (地址)和arsize (int 類型的整數) 數組名的類型爲 char *
cin.get() 不接收任何參數
cin.get(ch) 只有一個ch參數
在c中不可想象 參數數量不夠 或過多 會形成函數的出錯
可是在c++中,這種被稱爲函數重載的oop特性 容許重載建立多個同名函數,條件是它們的參數列表不一樣。若是c++中使用cin.get(name,arsize),則編譯器將找到使用char* 和int做爲參數的cin.get()版本。 若是沒有參數,則使用無參的版本。
4.文件尾條件
若是正常的文本中#正常的字符 那麼用#來用做結尾符不會合適
若是輸入來自於文件,可使用一種功能更增強大的技術 EOF 檢測文件尾。
c++輸入工具和操做系統協同工做,來檢測文件尾並將這種信息告知程序。
cin和鍵盤輸入 相關的地方是重定向 < 或者 >符號
操做系統容許經過鍵盤來模擬文件尾條件。
在unix中 能夠在行首按下CTRL+D來實現。
在windows命令提示符下,能夠在任意位置按CTRL+Z和enter
不少的pc編程環境都講CTRL+z視爲模擬的EOF
檢測EOF的方法原理:
檢測EOF後,cin將兩位(eofbit和failbit)都設置爲1;
查看eofbit是否被設置 使用成員eof()函數
若是檢測到EOF,則cin.eof()將返回bool值true,不然返回false。
一樣,若是eofbit或者fialbit被設置爲1,則fail()成員函數返回true,不然返回false。
eof()和fial()方法報告最近的讀取結果。所以應將cin.eof() 或者cn.fail()測試放在讀取後
#include <iostream>
int main()
{
using namespace std;
char ch;
int count=0;
cin.get(ch);
while(cin.fail()==false)//判斷cin.fail()的值 是否被設置,若是ctrl+z 和enter 已經按下 則返回true 條件不符合,跳出循環
{
cout<<ch;
++count;
cin.get(ch);
}
cout<<endl<<count<<"characters read\n";
return 0;
}
程序說明:windows 7系統上運行該程序,所以能夠按下ctrl+z和回車鍵來模擬EOF條件,在unix和類unix(包括linux)等系統張,用戶能夠按ctrl+z組合鍵將程序掛起,而命令fg恢復程序的執行。
經過使用重定向,能夠用此程序來顯示文本文件。並報告它包含的字符數 主要在unix中測彷佛
1.EOF結束輸入
cin方法檢測到EOF時,將設置cin對象中一個指示EOF條件的標記。設置這個標記後,cin將不讀取輸入,再次調用cin也無論用。
上面的狀況 是對文件的輸入有道理,由於程序不該該讀取超出文件尾的內容
對於鍵盤的輸入,有可能使用模擬EOF來結束循環,可是稍後還要讀取其餘輸入。
cin.clear()方法用來清除EOF標記,使輸入繼續進行。
在有些系統中CTRL+Z實際上會結束輸入和輸出。 而cin.clear()將沒法恢復輸入和輸出。
2.常見的字符輸入作法
每次讀取一個字符,直到遇到EOF的輸入循環的基本設計以下:
cin.get(ch);
while(cin.fail()==false) //這裏能夠用!邏輯運算符 去掉等號 while(!cin.fail())
{
...
cin.get(ch);
}
方法cin.get(char)的返回值是一個cin對象。然而,istream類提供了一個可將istream對象轉換爲bool值的函數;
當cin出如今須要bool值的地方(如在while循環的測試條件中)時,該轉換函數將被調用。另外,若是最後一次讀取成功了,則轉換獲得的bool值爲true;不然爲false。
意味着能夠改寫爲
while(cin)//while input is successful
這樣比!cin.fail()或者!cin.eof()更通用,由於它能夠檢測到其餘失敗的緣由,如磁盤故障
最後,因爲cin.get(char)的返回值爲cin,所以能夠將循環精簡成這種格式:
while(cin.get(sh))
{
...
}
這樣 cin.get(char)只被調用了一次,而不是兩次:循環前一次、循環結束後一次。爲判斷循環測試條件,程序必須首先調用cin.get(ch).若是成功,則將值放入ch中。而後,程序得到函數調用的返回值,即cin。以後對cin進行bool轉換,若是輸入成功,則結果爲true,不然爲false。三條指導原則 所有被放在循環條件中。
5.另外一個cin.get()版本
舊式的c代碼 i/o函數 getchar()和putchar()函數 仍然適用,只要像c語言中那樣包含頭文件stdio.h或者新的cstdio便可。
也可使用istream和ostream類中相似功能的成員函數。
cin.get() 不接受任何參數 會返回輸入中的下一個字符
ch=cin.get();
與getchar()類似,將字符編碼做爲int類型返回;
cn.get(ch)則會返回一個對象,而不是讀取的字符。
cout.put(ch) 顯示字符 此函數相似於c中的putchar() 只不過參數類型爲char 而不是int
put()最初只有一個原型 put(char) ,能夠傳遞一個int參數給它,該參數將被強制轉換爲char。c++標準還要求只有一個原型。而後c++的有些實現都提供了3個原型
參數分別爲char signed char unsigned char 。這些實現中若是給put()傳遞一個int參數將致使錯誤消息,由於轉換int的方式不止一種,若是使用顯式強制類型轉換的原型(cin.put(char(ch)))可使用int參數
成功使用cin.get() 須要瞭解EOF條件。
當函數到達EOF時,將沒有能夠返回的字符。相反,cin.get()將返回一個用符號常量EOF表示的特殊值。該常量是在頭文件iostream中定義的。EOF值必須不一樣於任何有效的字符值,以便程序不會將EOF於常規字符混淆。一般EOF被定義爲值-1,由於沒有ascii碼爲-1的字符,但並不須要知道它實際的值,在程序中使用EOF便可。
示例:
char ch; 替換爲 int ch;
cin.get(ch); 替換爲 ch=cin.get();
wihile(cin.fail()==false) 替換爲ch!=EOF
{
cout<<ch; 替換爲:ch.put(ch);
++count;
cin.get(ch); 替換爲:ch=cin.get();
}
上面代碼的功能是 若是ch是一個字符,則循環將顯示它,若是ch爲EOF 則循環將結束
須要知道的是 EOF不表示輸入中的字符,而是指出沒有字符。
//testin4.cpp
#include <iostream>
int main(void)
{
using namespace std;
int ch;
int count=0;
while((ch=cin.get())!=EOF) //獲取字符 並將字符的int型的編碼賦值給ch 而後再判斷與-1(EOF)是否相等
{
cout.put(char(ch)); //將int型編碼的ch 強制轉換爲char類型 而且輸出
++count;
}
cout<<endl<<count<<"characters read\n";
return 0;
}
注意,有些系統 若是對EOF不徹底支持,若是使用cin.get()來鎖住屏幕直到能夠閱讀它,將不起做用,由於檢測EOF時將禁止進一步讀取輸入。可使用循環計時來使屏幕停留一段時間。 也可使用cin.clear來重置輸入流。
儘可能使用cin.get(char) 這種返回cin對象的方式,get成員函數的主要用途是可以將愛那個stdio.h的getchar()和putchar()函數轉換爲iostream的cin.get()和cout.put()方法。只要用頭文件iostream替換爲stdio.h 並用做用類似的方法替換全部的getchar()和putchar()便可。
6.嵌套循環和二維數組
1. int a[4][5]二維數組
a[0] 爲二維數組的首元素,而首元素又是包含5個元素的數組
2.for(int row=0;row<4;row++) //行
{
for(int col=0;col<5;++col)//列
cout<<a[row][col]<<"\t";
cout<<endl;
}
3.初始化二維數組
int a[2][3]={
{15,25,35},{17,28,49}
}
也能夠 int a[0]={15,25,35}
int a[1]={17,28,49}
4.使用二維數組
//nested.cpp
#include <iostream>
const int Cities=5;
const int years=4;
int main()
{
using namespace std;
const char * cities[Cities]= //指針數組
{
"girbble city","gribbletown","new grebble","san gribble","gribble vista"
};
int maxtemps[years] [Cities]= //二維數組
{
{96,100,87,101,105},{96,98,91,107,104},{97,101,93,108,107},{98,103,95,109,108}
};
cout<<"maximum temperatures for 2008 -2011\n\n";
for(int city=0;city<Cities;++city)
{
cout<<cities[city]<<"\t";
for(int year=0;year<years;++year)
cout<<maxtemps[year][city]<<"\t";
cout<<endl;
}
return 0;
}
在此例中,可使用char數組的數組,而不是字符串指針數組
char cities[Cities][25]
{
"Gribble City",
"Gribbletown",
"New Gribble",
"San Gribble",
"Gribble Vista"
};
此種方法將所有5個字符串的最大長度限制爲24個字符。指針數組存儲5個字符串的地址,而使用char數組的數組時,將5個字符串分別賦值到5個包括25個元素的char數組中。所以,從存儲空間的角度說,使用指針數組更爲經濟;可是char數組的數組更容易修改其中的一個字符串。
方法2:
可使用string對象數組 而不是字符串指針數組
const string cities[Cities]={
"Gribble City",
"Gribbletown",
"New Gribble",
"San Gribble",
"Gribble Vista"
}
若是但願字符串是可修改的,則應該省略限定符const。使用string 對象數組時,初始化列表和用於顯示字符串的for循環代碼與前兩種方法中相同。在但願字符串是可修改的狀況下,string類自動調整大小的特性將使這種方法比使用二維數組更爲方便。
第六章 分支語句
1.
if(test-condition)
statement
2.
if(test-condition)
statement1
else
statement2
3.格式化if else 語句
if(){}
else{}
或者
if(){} else if(){} else{}
tips:條件運算符和錯誤防範
variable==value 反轉爲value==variable
以此來捕獲將相等運算符誤寫爲賦值運算符的錯誤
意思就是 if(3==a) 能夠避免寫成if(3=a) 編譯器會生成錯誤的信息,由於它覺得程序員試圖將一個值賦值給一個字面值(3老是等於3,而不是將另外一個值賦值給它)
假如省略了其中的一個等號 if(a=3) 就不會出現錯誤信息 沒有 if(3=a) 更好
2.邏輯表達式
a || 或運算符 比關係運算符的優先級要低
此運算符是一個順序點 sequence point 也就是說 先修改左側的值,再對右側的值進行斷定c++11側說法是 運算符左邊的子表達式先於右邊的子表達式
若是左側的表達式爲true 則c++將不會去斷定右側的表達式
cin>>ch;
if(ch=='y'||ch=='Y')
cout<<"You were warned!\a\a\n";
else if(ch=='n'||ch=='N')
cout<<"A wise choice ... bye\n";
else ....
程序只讀取一個字符,因此只讀取響應的第一個字符。這意味着用戶能夠用NO! 而不是N 進行回答,程序將只讀取N。然而,若是程序後面再讀取輸入時,將從O開始讀取。
b && 邏輯與表達式 同或運算符同樣 也是順序點 也是左側肯定true後,不看右側
用&&來設置取值範圍
&&運算符還容許創建一系列 if else if else語句,其中每種選擇都對應於一個特定的取值範圍。
例如
if(age>=85&&age<=100)
else if(age>=70&&age<85)
...
c 邏輯NOT運算符!
取反 優先級高於全部的關係運算符和算數運算符
d 邏輯運算符細節
c++邏輯or和邏輯and運算符的優先級都低於關係運算符。
c++確保程序從左向右進行計算邏輯表達式,並在知道答案後馬上中止
e 其餘表示方式
並非全部的鍵盤都提供了用做邏輯運算符的符號 因此 c++標準提供了另外一種表示方式
就要使用到and or not 等c++保留字 可是不是關鍵字 若是c語言想將其用做運算符 須要包含頭文件iso646.h
3.字符函數庫cctype
一個與字符相關的 很是方便的函數軟件包 主要用來測試字符類型
頭文件 cctype 老式的爲ctype。h
cin.get(ch);
if(isalpha(ch)) 判斷是否爲字母字符
4.?:運算符 三目運算符
5>3?10:12
if(5>3)
return 10;
else
return 12;
示例2:
(i<2)? !i ? x[i] : y : x[1];
嵌套三目運算符
5.switch
swithc(integer expression)//其中括號中的表達式的值爲label1 或者label2 或者其餘(default)
{
case label1:statement(s);
break; //跳出循環
case label2:
case label3:statement(s);//2和3同樣 都會執行後面的語句
default:statement(s)
}
5.1將枚舉量用做標籤
enum{red,orange,yellow,green,blue};//枚舉量
int main()
{
int code;
cin>>code;
switch(code)
{
case red:cout<<"red";
break;
case orange:cout<<"orange";
break;
defalut:....
}
}
5.2 switch 和if else 的區別
if else 更加通用 並且能夠處理區間
if(age>17&& age<50)
6.break和continue 語句
continue 是直接跳到循環開始處 開始下一輪循環 不會跳過循環的更新表達式 for循環中 continue語句使程序直接跳到更新表達式處,而後跳到測試表達式處。
可是對於while循環 continue會直接跳到測試表達式處 在while循環中 位於continue以後的更新表達式都將被跳過
break是跳出循環 結束包含它的循環
goto label; 直接將程序跳到label處
7.讀取數字的循環
讀入數字
int n;
cin>>n;
若是是字符不是數字 會發生以下的狀況
n的值不變
不匹配的輸入將被留在輸入隊列中
cin對象中的一個錯誤標記被設置
對cin方法的調用將返回false 若是被轉換爲bool類型
方法返回false 意味着能夠用非數字輸入來結束讀取數字的循環。非數字輸入設置錯誤標記意味着必須重置該標記,程序才能繼續讀取輸入。
clear()方法重置錯誤輸入標記,同時也重置文件尾 EOF條件 輸入錯誤和EOF都會致使cin返回false
示例
//cinfish.cpp
#include <iostream>
const int Max=5;
int main()
{
using namespace std;
double fish[Max];
cout<<"please enter the weights of your fish.\n";
cout<<"fish #1: ";
int i=0;
while(i<Max&&cin>>fish[i])// 左側的表達式爲假 則不會判斷右側的表達式,右側的表達式判斷的同時會將結果存儲到數組中
{
if(++i<Max) //執行++i i的值會變化
cout<<"fish #"<<i+1<<": ";
}
double total=0.0;
for(int j=0;j<i;j++)
total+=fish[j];
if(i==0)
cout<<"no fish\n";
else
cout<<total/i<<"=average weight of " <<i<<" fish\n";
cout<<"done.\n";
return 0;
}
上面的程序當輸入不是數字時,該程序將不在讀取輸入隊列,程序將拒絕輸入;若是想繼續讀取輸入,則須要重置輸入
當輸入錯誤的內容時:採起的3個步驟
1.重置cin以接受新的輸入
2.刪除錯誤輸入 (如何刪除錯誤的輸入) 程序必須先重置cin 而後才能刪除錯誤輸入
3.提示用戶再輸入
//cingolf.cpp
#include <iostream>
const int Max=5;
int main()
{
using namespace std;
int golf[Max];
cout<<"please enter your golf scores.\n";
cout<<"you must enter "<<Max<<" rounds.\n";
int i;
for(i=0;i<Max;i++)
{
cout<<"round #"<<i+1<<": ";
while(!(cin>>golf[i]))//用戶輸入數字 則cin表達式將爲true 則將後面的值存入到數組中,若是輸入的是非數字值,則cin爲false 則值不會存入到數組中
{
cin.clear();//若是沒有此語句,則程序會拒絕繼續讀取輸入
while(cin.get()!='\n')//讀取行尾以前的全部輸入,從而刪除這一行中的錯誤輸入;另外一種方法是讀取到下一個空白字符,這樣將每次刪除一個單詞,而不是一次刪除整行。
continue;
cout<<"please enter a number: ";
}
}
double total=0.0;
for(int i=0;i<Max;i++)
total+=golf[i];
cout<<total/Max<<"=average scores " <<Max<<" rounds\n";
cout<<"done.\n";
return 0;
}
8.簡單文件的輸入和輸出
1.文本i/o和文本文件
cin輸入,會將輸入看做一系列的字節,每一個字節被解釋爲字符編碼,無論目標的數據類型是什麼,都被看做字符數據,而後cin對象負責將文本轉換爲其餘的類型。
輸入38.2 15.5
int n;
cin>>n 會不斷的讀取,直到遇到非數字字符, 38被讀取後 句點將成爲輸入隊列中的下一個字符
double x;
cin>>x; 不斷的讀取 直到遇到不屬於浮點數的字符。 38.2將會被讀取,空格將成爲輸入隊列中的下一個字符
char word[50];
cin>>word;
不斷讀取,直到遇到空白字符, 38.5被讀取,而後將這4個字符的編碼存儲到數組word中。並在末尾加上一個空字符。
char word[50];
cin.getline(word,50);
不斷讀取,直到遇到換行符(示例中少於50個字符),全部字符被存儲到word中 包括空格,並在末尾加上一個空字符。換行符被丟棄,輸入隊列中的下一個字符是下一行中的第一個字符。這裏不須要轉換。
對於輸入 將執行相反的轉換。即整數被轉換爲數字字符序列,浮點數被轉換爲數字字符和其餘字符組成的字符序列 字符數據不須要作任何的轉換。
輸入一開始都是文本,因此控制檯輸入的文件版本都是文本文件愛你,即每一個字節都存儲了一個字符編碼的文件。
2.寫入到文本文件中 相對應於寫入到控制檯上(屏幕 對應於cout)
cout用於控制檯輸出的基本事實
必須包含頭文件iostream
頭文件iostream定義了一個用處理可輸出的ostream類
頭文件iostream聲明瞭一個名爲cout的ostream變量(對象)
必須指明名稱空間std;
結合使用cout和運算符<<來顯示各類類型的數據
文件輸出與上述類似
必須包含頭文件fstream
頭文件fstream定義了一個用於處理輸出的ofstream類
須要聲明一個或多個ofstream變量(對象),並本身命名
指明名稱空間std
須要ofstream對象與文件關聯起來,爲此,方法之一是使用open()方法
使用完文件後,應好似用方法close()將其關閉
可結合使用ofstream對象和運算符<<來輸出各類類型的數據
cout爲ostream已經定義好的 因此不須要從新定義
而ofstream對象沒有已經定義好的對象 因此須要本身聲明此脆響 並同文件相關聯
ofstream outfile;
ofstream fout; 定義了兩個對象 都屬於ofstream類
關聯文件
outfile.open("fish.txt"); //關聯文件 fish.txt
char filename[50];
cin>>filename;
fout.open(filename); //關聯文件 filename變量存儲的值做爲文件名
使用方法:
double wt=125.8;
outfile<<wt; //將wt數據寫入到上面關聯的文件中 fish.txt
char line[81]="objects are closer than they appear";
fout<<line<<endl; //將line數組中的內容輸出到fout對象關聯的文件中去
聲明一個ofstream對象並將其同文件關聯起來後,即可以像使用cout那樣使用它。全部可用於cout的操做和方法均可以用於ofstream對象
主要步驟:
1.包含頭文件fstream
2.建立一個ofstream對象
3.將該ofstream對象同一個文件關聯起來。
4.就像使用cout那樣使用該ofstream對象
//outfile.cpp
#include <iostream>
#include <fstream>
int main()
{
using namespace std;
char automobile[50];
int year;
double a_price;
double d_price;
ofstream outfile;
outfile.open("carinfo.txt");//若是在當前目錄下沒有此文件怎麼辦呢?自動建立 ;若是已經存咋這個文件,默認狀況下會刪除原來的內容(將此文件的長度截短爲0),寫入新的內容。17章可根據參數修改 此默認設置
cout<<"enter the make and model of automobile: ";
cin.getline(automobile,50);//讀取到換行符爲止
cout<<"enter the model year: ";
cin>>year;
cout<<"enter the original asking price: ";
cin>>a_price ;
d_price=0.913*a_price;
cout<<fixed;
cout.precision(2);
cout.setf(ios_base::showpoint);//顯示固定格式
cout<<"make and model: "<<automobile<<endl;
cout<<"year: $"<<year<<endl;
cout<<"was asking $"<<a_price<<endl;
cout<<"now asking $"<<d_price<<endl;
outfile<<fixed;
outfile.precision(2);//格式化方法
outfile.setf(ios_base::showpoint);//格式化方法
outfile<<"make and model: "<<automobile<<endl;
outfile<<"year: "<<year<<endl;
outfile<<"was asking $" <<a_price <<endl;
outfile<<"now asking $"<<d_price <<endl;
outfile.close();//不須要使用文件名做爲參數
return 0;
}
全部的cout方法均可以用outfile對象使用
關聯文件後,須要關閉此文件的關聯
3.讀取文本文件 相似於cin 即istream類和ifstream類對應
cin 將控制檯(鍵盤的內容輸入) 存入到變量中
ifstream類定義的對象 從文本文件中讀取內容到指定的變量中
首先看控制檯:
包含iostream頭文件
此頭文件定義了一個用處理輸入的istream類
頭文件iostream聲明瞭一個名爲cin的istream變量 對象
指明命名空間
結合cin和get方法來讀取一個字符 使用cin和getline來讀取一行字符
cin 和eof()、fial()方法來判斷輸入是否成功。
cin自己用做測試條件,若是最後一個讀取操做成功,它將轉換爲bool的值,true或者false
文件的輸入與上面的cin相似
頭文件fstream
頭文件fstream定義了一個用於處理輸入的ifstream類
須要聲明一個或多個ifstream變量(對象),並以本身喜歡的方式對其進行命名,條件是遵照經常使用的命名規則。
指明名稱空間std
將ifstream對象與文件關聯起來。方法之一是使用open方法
使用完文件後,須要使用close()方法將其關閉
ifstream對象和運算符>>來讀取各類類型的數據
ifstream和eof() fail()等方法來判斷輸入是否成功
ifstream對象自己被用做測試條件時,若是最後一個讀取操做成功,它將被轉換爲布爾值bool
示例:
ifstream infile;
ifstream fin;
infile.open("bowling.txt");
char filename[50];
cin>>filename;
fin.open(filename);
double wt
infile>>wt;
char line[81];
fin.getline(line,81);
若是試圖打開一個不存在的文件用於輸入,則這種錯誤將致使後面使用ifstream對象進行輸入時失敗,檢查文件是否被成功打開的首先方法是使用is_open(),爲此可使用相似於下面的代碼:
infile.open("bowling.txt");
if(!infile.is_open())
{
exit(EXIT_FAILURE);
}
若是文件被成功打開,方法is_open()將返回true;不然返回false
exit()函數的原型是在頭文件cstdlib中定義的。在該頭文件中,還定義了一個用於同操做系統通訊的參數值EXIT_FAILURE 函數exit()終止程序。
若是編譯器不支持is_open()函數 可使用比較老的方法good()來代替。可是此函數在檢查問題方面沒有新的函數is_open()那麼普遍。
示例
//sumafile.cpp
#include <iostream>
#include <fstream>
#include <cstdlib>
const int SIZE=60;
int main()
{
using namespace std;
char filename[SIZE];
ifstream infile;//定義對象
cout<<"enter name of data file: ";
cin.getline(filename,SIZE);//從文件中獲取一行數據 60個字符
infile.open(filename);//關聯指定的文件
if(!infile.is_open())//判斷文件是否存在 若是不存在則提示 而且退出
{
cout<<"could not open the file"<<filename<<endl;
cout<<"program terminating.\n";
exit(EXIT_FAILURE);//根據EXIT_FAILURE的值 退出返回
}
double value;
double sum=0.0;
int count=0;
infile>>value;//從文件獲取值 存入變量
while(infile.good())//判斷獲取的數據是否爲double類型
{
++count;
sum+=value;
infile>>value;//獲取下一個值
}
if(infile.eof())//判斷是否到文件結尾
cout<<"end of file reached.\n";
else if(infile.fail())//判斷是否數據匹配
cout<<"input terminated by data mismatch.\n";
else
cout<<"input terminated for unknown reason.\n";
if(count==0)//
cout<<"no data processed.\n";
else//最後輸出各個變量的值
{
cout<<"items read: "<<count<<endl;
cout<<"sum: "<<sum<<endl;
cout<<"average: "<<sum/count<<endl;
}
infile.close();
return 0;
}
1.程序說明 設置中文路徑名稱可用
setlocale(LC_ALL,"Chinese-simplified")
2.注意文件的擴展名
1.txt.txt
3.加入絕對路徑
infile.open(「c://source/1.txt」);
4.文件讀取循環的正確設計。讀取文件時,有幾點須要檢查,首先,程序讀取文件時要超過EOF,若是遇到EOF 則方法eof會返回true;其次類型不匹配 fail返回true;若是最後一次讀取文件受損或因噶進故障,bad會返回true 因此不要分別檢查這些問題,用good()方法一次性檢查就能夠了。若是沒有任何問題則返回true
注意上面檢測的順序,首先是eof 以後是fail 再以後是bad
同cin相同
cin>>value; //循環以前讀入
while(){
...
cin>>value; 循環中讀入
}
同上
infile>>value;//循環以前讀入
while(infile.good())
{
...
infile>>value;//循環以後讀入
}
能夠合併爲一個判斷的表達式
while (infile>>value)
{
statements;
}
第七章 函數 ----c++的編程模塊
函數原型 prototype
函數定義 definition
調用函數 calling
1.定義函數
有返回值和無返回值的函數
int functionName(parameterList) 有返回值
{
statements(s)
return value; 必須存在
}
無返回值
void functionname(parameterlist)
{
statements(s);
return; 無關緊要 不返回任何值
}
2.函數原型和調用
系統庫提供的函數的原型通常包含在include文件中 頭文件中。
提供函數原型的緣由以下:
a 原型描述了函數到編譯器的接口,它將函數返回值的類型以及參數的類型和數量告訴編譯器。
double volume=cube(side);
首先,原型告訴編譯器,cube有一個double參數 ,若是程序沒有提供這樣的參數,原型將讓編譯器可以捕獲這種錯誤。其次,cube函數完成計算後,將把返回值放置在指定的位置--多是cpu寄存器,也多是內存中。而後調用函數將從這個位置取得返回值。
由於原型指出了cube的類型爲double 所以編譯器知道應該檢索多少個字節以及如何解釋它們。
另外一個方面是,若是沒有原型,編譯器就會在程序中查找,致使效率不高,編譯器在搜索文件的剩餘部分時將必須中止對main()的編譯
另外一個問題是,函數可能並不在文件中,c++容許將一個程序放在多個文件中,單獨編譯這些文件,而後再將他們組合起來。
3.原型的語法
函數原型是一條語句,所以必須以分號結束。
複製函數頭部加上分號 就是原型
原型中能夠沒有變量名,可是必須有類型
4.原型的功能 c++中必須提供函數的原型
編譯器正確處理函數的返回值
編譯器檢查使用的參數數目是否正確
編譯器檢查使用的參數類型是否正確。若是不正確,則轉換爲正確的類型
調用函數cube (2) 原型爲cube(double)
因爲原型中指定了double類型 因此會將整型的2轉換爲2.0再參加運算
可是若是反過來 cube(int ) 調用時使用cube(2.7854)
原型爲int型 轉換時會將浮點的類型轉換爲int 會致使數據的丟失
二 函數參數和按值傳遞
1.按值傳遞時,不會改變實參的數值
將實參複製一個副本 成爲形參的值,在函數中計算的是形參的數值
2.多個參數
逗號分隔多個參數
和一個參數同樣,原型中沒必要與定義中的變量名相同,並且能夠省略
可是在原型中提供變量名使得程序更加容易理解
三函數和數組
示例:int sum_arr(int arr[],int n) 爲原型
調用:int sum_arr(int arr[],int n)
也可使用int sum_arr(int * arr,int n)
1.函數如何使用指針來處理數組
將數組名視爲指針
cookies==&cookies[0]
首先,數組聲明使用數組名來標記存儲位置;其次,對數組名使用sizeof將獲得整個數組的長度 以字節爲單位ie;第三,將地址運算符&用於數組名時,將返回整個數組的地址
原型:int sum_arr(int * arr,int n) 使用int arr[] 是相同的 可是後者提醒 指針指向數組的第一個元素
調用:int sum_arr(cookies,arsize)
兩個恆等式:
arr[i]==*(ar+i)
&arr[i]==ar+i
將指針(包括數組名)加1 實際上加上了一個指針指向的類型的長度 字節爲單位ie 對於便利數組而言,使用指針加法和數組下標時等效的
2.將數組做爲參數意味着什麼?
上例中 實際上傳遞的是指向數組的指針,而不是整個數組。
當傳遞常規變量是,使用的是該變量的拷貝;可是傳遞數組時,函數將使用原來的數組。可是傳遞數組指針時,使用的是原來的數組,並非數組的copy
雖然節省了資源,可是同時帶來了對原始數據被破壞的風險。
可使用const的限定符這種解決辦法。
cookies和arr指向同一個地址,可是sizeof cookies 的值是32 而sizeof arr爲4 這是由於sizeof cookies 是整個數組的長度,而sizeof arr只是指針變量自己的長度。順便說,這也是必須顯式傳遞數組長度,而不能在sum_arr()中使用sizeof arr的緣由 指針並無指出數組的長度
在原型中不要使用方括號來傳遞數組的長度
void fillarray(int arr[size])
3.更多數組函數示例
//7.7 arrfun3.cpp
#include <iostream>
const int Max=5;
int fill_array(double ar[],int limit);//原型 fill_array
void show_array(const double ar[],int n);//原型 show_array
void revalue(double r,double ar[],int n);//原型 revalue
int main(){
using namespace std;
double properties[Max];
int size=fill_array(properties,Max);//調用填充函數 注意參數
show_array(properties,size);//調用顯示函數
if(size>0)//大於0 說明fill_array函數返回的值是 填充成功的數量
{
cout<<"enter revaluation factor: ";
double factor;
while(!(cin>>factor)) //輸入的值各類緣由不成功 每次讀取一個字符
{
cin.clear();//重置輸入
while(cin.get()!='\n') //再次獲取輸入 若是不等於換行符 則繼續讀取 一直到換行符以前
continue;
cout<<"bad input,please enter a number: ";
}
revalue(factor,properties,size);//從新賦值
show_array(properties,size);//再次調用顯示函數
}
cout<<"donw.\n";
cin.get();
cin.get();//等待 顯示
return 0;
}
int fill_array(double ar[],int limit)//填充數組的實現
{
using namespace std;
double temp;
int i;
for(i=0;i<limit;i++)
{
cout<<"enter value #"<<(i+1)<<": ";
cin>>temp;
if(!cin)
{
cin.clear();
while(cin.get()!='\n')
continue;
cout<<"bad input;input process terminated .\n";
break;
}
else if(temp<0)//輸入的是賦值則跳出循環
break;
ar[i]=temp;//數組賦值
}
return i;
}
void show_array(const double ar[],int n)//顯示數組中各個元素的值 這裏的const 用來保證不會修改原始的數組;
{
using namespace std;
for(int i=0;i<n;i++)
{
cout<<"property #"<<(i+1)<<":$";
cout<<ar[i]<<endl;
}
}
void revalue(double r,double ar[],int n)//從新賦值
{
for(int i=0;i<n;i++)
ar[i]*=r;
}
程序說明 關於void show_array(const double ar[],int n);
指針ar不能修改該數據,也就是說可使用像ar[0]這樣的值,但不能修改,注意,這並不意味着原始數組必須是常量,而只是意味着不能在show_array()函數中使用ar來修改這些數據,所以,show_array()將數組視爲只讀數據。
假設無心間在show_array()函數中執行了下面的操做,從而違反了這種限制:
ar[0]+=10; 會提示錯誤
c++聲明const double ar[] 解釋爲const double * ar 所以該聲明其實是說 ar指向的是一個常量值。
首先考慮的是經過數據類型和設計適當的函數來處理數據 而後將這些函數組合成一個程序。 叫作自下而上的程序設計
c語言爲過程性編程 傾向於從上而下的程序設計
4.使用數組區間的函數
處理數組的c++函數,必須將數組中的數據種類,數組的起始位置和數組中元素數量提交給它;
另外一種處理數組的方法,即指定元素區間range 能夠經過傳遞兩個指針來完成。一個指針標識數組的開頭 另外一個指針標識數組的尾部
sum=sum_arr(cookies,cookies+arsize);
sum=sum_arr(cookies,cookies+3);
sum=sum_arr(cookies+4,cookies+8);
//arrfun4.cpp
#include <iostream>
const int arsize=8;
int sum_arr(const int * begin,const int * end);
int main()
{
using namespace std;
int cookies[arsize]={1,2,4,8,16,32,64,128};
int sum=sum_arr(cookies,cookies+arsize);
cout<<"total cookies eaten: "<<sum<<endl;
sum=sum_arr(cookies,cookies+3);
cout<<"first three eaters ate "<<sum<<"cookies.\n";
sum=sum_arr(cookies+4,cookies+8);
cout<<"last four eaters ate "<<sum<<" cookies.\n";
return 0;
}
int sum_arr(const int * begin,const int * end)
{
const int * pt;
int total=0;
for(pt=begin;pt!=end;pt++)
total=total+*pt;
return total;
}
pt設置爲指向要處理的第一個元素 begin指向的元素的指針,並將*pt 加入到total中,而後循環經過遞增操做來更新pt。使之指向下一個元素。只要pt不等於end 這個過程就會繼續。
end-begin爲整數值 等於數組的元素數目
5.指針和const
兩種不一樣的方式將const關鍵字用於指針。
第一是讓指針指向一個常量對象,這樣能夠防止使用該指針來修改所指向的值
第二是將指針自己聲明爲常量,能夠防止改變指針指向的位置
int age=39;
const int * pt=&age;
pt指向一個const int的值,因此不能用pt來修改這個值, 換句話說就是* pt的值爲const 不能被修改;
*pt+=1; 錯誤的語句
cin>>*pt;
pt的聲明並不意味着它指向的值其實是一個常量,而只是說對pt而言,這個值是常量。例如pt指向age 而age不是const 能夠直接經過age變量來修改age的值
*pt=20; 錯誤
age=20 正確
將常規變量的地址賦給常規指針 可行
將const變量的地址賦給指向const的指針 可行 變量和指針都不能修改原值
將const的地址賦給常規指針 不可行 (緣由是常規指針可能會改變指向的const的值 會引起錯誤)
例2:一級指針 一級間接關係 將非const指針賦給const指針是能夠的
int age=39; //age++ 有效
int * pd=&age; //*pd=41 有效
const int * pt=pd; //*pt=42 無效
二級關係
const int **pp2; //二級指針
int * p1;
const int n=13; //常量 變量
pp2=&p1; // 錯誤 將非const地址 賦給const指針 pp2 所以可使用p1來修改const數據,
*pp2=&n; //常量變量的地址賦給一級指針 有效 兩個都是常量
*p1=10; //
若是數據類型自己並非指針,則能夠將const數據或非const數據的地址賦給指向const的指針,但只能將非const數據的地址賦給非const指針。
二級指針不容許賦值
禁止將常量數組的地址賦給很是量指針將意味這不能將數組名做爲參數傳遞給使用很是量形參的函數
const int months[12]={31,28,31,30,31,30,31,31,30,31,30,31};
int sum(int arr[],int n);
int j=sum(months,12);
試圖將const指針賦給非const指針arr 編譯器將禁止這種函數調用
儘量使用const
TIPS:將指針參數聲明爲指向常量數據的指針有兩條理由:
避免無心間修改數據而致使的編程錯誤
使用const使得函數可以處理const和非const實參,不然將只能接受非const數據
int age=39;
const int * pt=&age;
上面的聲明中的const只能防止修改pt指向的值(39),而不能防止修改pt的值。也就是說,能夠將一個新的地址賦值給pt:
int sage=80;
pt=&sage; 不能使用pt來修改sage的值 可是能夠修改pt自己的值 (地址值)
使用const的方式使得沒法修改指針的值
int sloth=3;
const int * ps=&sloth;//指針指向一個整型的常量數值 ----1
int * const finger=&sloth;// 一個常量指針 指向整型-----2
1 不能經過指針 改變指向的常量的值,可是能夠改變指針自己的地址值 const的位置在最前方 能夠不指向sloth
2 不能改變指針自己的地址值,可是能夠經過此指針修改指向變量的值 const的位置在後方 執政指向sloth
總結 finger和*ps都是const;而*finger和ps不是const
double trouble=2.0e30;
const double * const stick=&trouble;
stick 只能指向trouble stick不能用來修改trouble的值, stick和*stick都是const
一般 將指針做爲函數參數來傳遞時,可使用指向const的指針來保護數據。
四 函數和二維數組
爲編寫將二維數組做爲參數的函數 數組名是地址 ,相應的形參就是一個指針 數組名錶明數組組中的第一個元素的地址 &數組名則表明所有數組的地址
int data[3][4]={{1,2,3,4},{9,8,7,6},{2,4,6,8}};
int total=sum(data,3);
data是一個數組名,該數組有3個元素,第一個元素自己是一個數組,有4個int值組成,所以data的類型是指向4個int組成的數組的指針。
data指向二維數組中的第一個元素,表明{1,2,3,4}這個元素的地址
因此正確的原型以下:
int sum(int (*ar2)[4],int size); 可讀性差一些
ar2 接收的就是一個包含4個int型元素的數組的地址值 (指針)
而與上面一樣的原型則爲
int sum(int ar2[][4],int size) 可讀性更好
ar2是指針而不是數組,指針類型指出,由4個int組成的數組,所以,指針類型指定了列數,這就是沒有將列數做爲獨立的函數參數進行傳遞的緣由。
示例:
int a[100][4];
int b[6][4];
int total1=sum(a,100);
int total2=sum(b,6);
示例2:
int sum(int ar2[][4],int size)
{
int total=0;
for(int r=0;r<size;r++) //雙層循環
for(int c=0;c<4;c++)
total+=ar2[r][c];
return total;
}
ar2+r指向編號爲r的元素,所以ar2[r]是編號爲r的元素,因爲該元素自己是一個4個int組成的數組,所以ar2[r]是由4個int組成的數組的名稱。
將下標用於數組名將獲得一個數組元素,所以ar2[r][c]是由4個int組成的數組中的一個元素,是一個int值。必須對指針ar2執行兩次解除引用
最好的方法是兩次方括號 ar2[r][c]
也可使用運算符*兩次
ar2[r][c]==*(*(ar2+r)+c)
ar2 指向二維數組的第一行
ar2+r 指向二維數組中的第r行
*(ar2+r) 指向二維數組中的第r行中的首元素
*(ar2+r)+c 指向二維數組中的第r行中的標籤爲c的元素
*(*(ar2+r)+c) 表明二維數組中的第r行中的標籤爲c的元素的數值
sum()的代碼在聲明參數ar2時,沒有使用const 由於這種技術只能用於指向基本類型的指針,而ar2是指向指針的指針。
五 函數和c風格字符串
c風格字符串 一些列字符組成,以空值字符結尾。
將字符串做爲參數時意味着傳遞的是地址,可是可使用const來禁止對字符串參數進行修改。
1.將c字符串做爲函數的參數
char數組
用引號括起的字符串常量 也稱字符串字面量
被設置爲字符串的地址的char指針
上面的三個都是char *
示例:char gohst[15]="asdfasdf";
char * str="adsfasdf";
int n1=strlen(ghost); ghost表明&ghost[0]
int n2=strlen(str); 指向char的指針
int n3=strlen(「adsfasdf」);括號中字符串的地址
因此 將字符串做爲參數來傳遞,但實際傳遞的是字符串第一個字符的地址 字符串函數原型應將其表示字符串的形參聲明爲char * 類型
c風格字符串與常規char數組的一個重要區別是 字符串有內置的結束字符 但不以空值字符結尾的char數組只是數組,而不是字符串。
長度能夠不用傳遞給函數 由於 函數能夠檢測字符串中的每一個字符 若是遇到空值字符 就是中止。
示例:
#include <iostream>
unsigned int c_in_str(const char * str,char ch)
int main()
{
using namespace std;
char mmm[15]="minimum";
char * wail="ululate";
unsigned int ms=c_in_str(mmm,'m');
unsigned int us=c_in_str(wail,'u');
cout<<ms<<"m characters in"<<mmm<<endl;
cout<<us<<"u characters in"<<wail<<endl;
return 0;
}
unsined int c_in_str(const char * str,char ch)
{
unsigned int count=0;
while(*str)
{
if(*str==ch)
count++;
str++;
}
return count;
}
一樣可行的方式:unsigned int c_in_str(const char str[],char ch)
函數自己演示了處理字符串中字符的標準方式
while(* str)
{
statements
str++;
}
其中*str表示的是第一個字符
當str指向空值字符時,*str等於0 表示空值字符的數字編碼爲0 則循環結束
函數沒法返回一個字符串,可是能夠返回一個字符串的地址,並且效率更高
示例2 strgback.cpp
#include <iostream>
char * buildstr(char c,int n); 原型
int main()
{
using namespace std;
int times;
char ch;
cout<<"enter a character: ";
cin>>ch; \\一個字符
cout<<"enter an integer: ";
cin>>times; \\一個整數
char *ps=buildstr(ch,times); \\返回地址
cout<<ps<<endl;
delete [] ps; \\清除ps指向的內存空間 數組
ps=buildstr('+',20);\\調用函數
cout<<ps<<"-done-"<<ps<<endl;
delete [] ps;
return 0;
}
char * buildstr(char c,int n)
{
char * pstr=new char[n+1];\\申請空間 數組存放 pstr爲指向的指針
pstr[n]='\0'; \\下標爲n的數組值爲空值字符
while(n-->0) \\循環賦值
pstr[n]=c;
return pstr; \\返回字符數組的指針
}
建立包含n個字符的字符串,須要可以存儲n+1個字符的空間,以便可以存儲空值字符。
注意用new 申請的內存空間 必須記住用delete來釋放
6 函數和結構
結構和數組不一樣 結構變量更接近於基本的單值變量,將其數據組合成單個實體或數據對象,該實體被視爲一個總體。
能夠將一個結構賦給另一個結構。也能夠按值傳遞結構,就像普通變量那樣。
結構傳遞副本 函數也可返回結構
結構名不是地址,僅僅是結構的名稱 若是想要獲得結構的地址,必須使用地址運算符&
c++仍是用該運算符來表示引用變量 &
1.傳遞和返回結構
適合結構比較小的狀況
struct travel_time{ \\結構定義
int hours;
int mins;
}
travel_time sum(travel_time t1,travel_time t2); \\ 原型
travel_time day1={5,45};
travel_time day2={4,55};
travel_time trip=sum(day1,day2);\\調用
travel_time sum(travel_time t1,travel_time t2) \\被調用函數的實現
{
travel_time total;
statement;
return total; \\返回一個結構體變量
}
2.處理結構的示例
有關函數和c++結構的大部分知識均可以用於c++類中
示例:
\\strctfun.cpp
#include <iostream>
#include <cmath>
struct ploar \\用距離和角度來表示一個點
{
double distance;
double angle;
}
struct rect \\x和y的座標表示點的位置
{
double x;
double y;
}
ploar rect_to_polar(rect xypos);\\原型聲明
void show_polar(polar dapos);\\原型聲明
int main()
{
using namespace std;
rect rplace;\\結構類型聲明
ploar pplace;\\結構類型聲明
cout<<"enter the x and y values: ";
while(cin>>rplace.x>>rplace.y)\\注意這裏的cin>>rplace.x 返回cin類型 接着調用cin>>rplace.y
{
pplace=rect_to_polar(rplace);\\調用函數
show_polar(pplace);\\調用函數
cout<<"next two numbers (q to quit): ";
}
cout<<"done.\n";
return 0;
} \\end of main
ploar rect_to_polar(rect xypos) \\接受 rect結構體 返回ploar結構體
{
using namesapce std;
ploar answer;
answer.distance=sqrt(xypos.x*xypos.x+xypos.y*xypos.y); \\計算距離
answer.angle=atan2(xypos.y,xypos.x);\\計算角度
return answer;
}
void show_ploar(polar dapos)
{
using namespace std;
const double rad_to_deg=57.29577951;
cout<<"distance= "<<dapos.distance;
cout<<",angle="<<dapos.angle * rad_to_deg;\\角度轉換
cout<<"* degrees\n";
}
2.傳遞結構的地址
傳遞結構的地址而不是整個結構以節省時間和空間,使用指向結構的指針。
調用函數,將結構的地址 &pplace而不是結構自己pplace傳遞給它;
將形參聲明爲指向polar的指針,即ploar* 類型。因爲函數不該該修改結構,所以使用了const修飾符;
形參是指針而不是接哦股,所以應間接成員運算符-> 而不是成員運算符 句點。
修改後的代碼以下 show_polar(const ploar *pda)和rect_to_polar()
\\strctfun.cpp
#include <iostream>
#include <cmath>
struct ploar \\用距離和角度來表示一個點
{
double distance;
double angle;
}
struct rect \\x和y的座標表示點的位置
{
double x;
double y;
}
\\ploar rect_to_polar(rect xypos);\\原型聲明
void rect_to_polar(const rect * pxy,ploar * pda);\\原型聲明
void show_polar(const polar * pda);\\原型聲明
int main( )
{
using namespace std;
rect rplace;\\結構類型聲明
ploar pplace;\\結構類型聲明
cout<<"enter the x and y values: ";
while(cin>>rplace.x>>rplace.y)\\注意這裏的cin>>rplace.x 返回cin類型 接着調用cin>>rplace.y
{
\\pplace=rect_to_polar(rplace);\\調用函數
rect_to_polar(&rplace,&pplace);\\傳遞地址
show_polar(&pplace);\\調用函數 \\傳遞地址
cout<<"next two numbers (q to quit): ";
}
cout<<"done.\n";
return 0;
} \\end of main
ploar rect_to_polar(const rect * pxy,ploar * pda) \\接受 rect結構體 返回ploar結構體
{
using namesapce std;
pda->distance=sqrt(pxy->x*pxy->x+pxy->y*pxy->y); \\計算距離
pda->angle=atan2(pxy->y,pxy->x);\\計算角度
}
void show_ploar(const polar * pda)
{
using namespace std;
const double rad_to_deg=57.29577951;
cout<<"distance= "<<pda->distance;
cout<<",angle="<<pda->angle * rad_to_deg;\\角度轉換
cout<<"* degrees\n";
}
上例使用了指針 函數可以對原始的機構進行操做;而上上例使用的是結構副本。
7.函數和string對象
c風格字符串和string對象的用途相似,但與數組相比,string對象與結構更類似。
結構能夠賦值給結構,對象能夠賦值給對象。
若是須要多個字符串,能夠聲明一個string對象的數組,而不是二維char數組,操做會更加靈活。
//topfive.cpp
#include <iostream>
#include <string>
using namespace std;
const int SIZE=5;
void display(const string sa[],int n);\\原型
int main()
{
string list[SIZE]; \\string對象數組
cout<<"enter your "<<SIZE<<" favorite astronomical sights: \n ";
for(int i=0;i<SIZE;i++)
{
cout<<i+1<<": ";
getline(cin,list[i]);\\初始化數組中的元素
}
cout<<" your list: \n ";
display(list,SIZE); \\傳遞過去指向string對象數組的指針 (即地址值 =數組名)
return 0;
}
void display(const string sa[],int n)
{
for(int i=0;i<n;i++)
cout<<i+1<<" : "<<sa[i]<<endl;
}
8.函數與array對象
c++中 類對象基於結構
因此:能夠按值將對像傳遞給函數, 函數處理的是原始對象的副本。
能夠傳遞對象的指針 函數內部直接操做原始的對象 經過指針
使用array對象來存儲一年四個季度的開支:
std::array<double,4> expenses; 須要包含頭文件array,而名稱array位於名稱空間std中。
函數show(expenses) 不能修改expenses
函數fill(&expenses) 能夠修改expenses 傳遞進來的是指針
則函數的原型爲:
void show(std::array<double,4> da);
void fill(std::array<double,4> * pa); \\pa指向array對象的指針
能夠用符號常量來替換4
const int seasons=4;
const std::array<std::string,seasons> snames={"spring","summer","fall","winter"};
模板array並不是只能存儲基本數據類型,還能夠存儲類對象
//arrobj.cpp
#include <iostream>
#include <array>
#include <string>
const int seasons=4;
const std::array<std::string,seasons> snames={"spring","summer","fall","winter"}; //數組的聲明
void fill(std::array<double,seasons> * pa);//原型定義 接收一個array的對象的指針
void show(std::array<double,seasons> da);//原型定義 接收一個array的對象
int main()
{
std::array<double,seasons> expenses; //定義
fill(&expenses);//填充 指針指向的array對象定義的double數組
show(expenses);//傳值 而且顯示 數組中的內容
return 0;
}
void fill(std::array<double,seasons> * pa) //接收指向數組的指針
{
using namespace std;
for(int i=0;i<seasons;i++)
{
cout<<"enter "<<snames[i]<<" expenses: ";
cin>>(*pa)[i]; //填充 數組元素
}
}
void show(std::array<double,seasons> da)
{
using namespace std;
double total=0.0;
cout<<" \n EXPENSES \n ";
for(int i=0;i<seasons;i++)
{
cout<<snames[i]<<" :$ "<<da[i]<<endl;//輸出 個元素的值
total+=da[i];//相加各個元素的值
}
cout<<"total expenses: $ " <<total<<endl;
}
fill和show兩個函數都有缺點。函數show()存在的問題是,expenses存儲了四個double值,而建立一個新對象並將expenses的值賦值到其中的效率過低。
7.9遞歸
1.包含一個遞歸調用的遞歸
若是遞歸函數調用本身,則被調用的函數也將調用本身,這將無限循環下去,因此必須有終止調用鏈的內容。
void recurs(argumentlist)
{
statements1
if(test)
recurs(arguments)
statements2
}
示例:
recur.cpp
#include <iostream>
void countdown(int n)
int main()
{
countdown(4);
return 0;
}
void countdown(int n)
{
using namespace std;
cout<<"counting down ..."<<endl;
if(n>0)
countdown(n-1);
cout<<n<<":kaboom\n";
}
輸出:
counting down ...4
counting down ...3
......
counting down ...0
0:kaboom!
1:kaboom!
......
4:kaboom!
7.9.2包含多個遞歸調用的遞歸
將一項工做不斷分爲兩項或多項較小的工做時,遞歸會很是有用。
示例:繪製標尺 分而治之的策略
標出兩端,找到重點並將其標出 而後將一樣的操做用於標尺的左半部分和右半部分
//ruler.cpp
#include <iostream>
const int len=66;
const int divs=6;
void subdivide(char ar[],int low,int high,int level);//prototype
int main()
{
char ruler[len];//數組
int i;
for(i=1;i<len-2;i++)
ruler[i]=' ';//初始化數組中的值均爲空格
ruler[len-1]='\0';//數組中最後一個元素的值爲空值
int max=len-2;//數組下標範圍設置
int min=0;
ruler[min]=ruler[max]='|';//初始化數組首尾兩個元素的值爲‘|’
std::cout<<ruler<<std::endl;//輸出數組各項的值
for(i=1;i<=divs;i++)//6次循環 分別設置數組中的值 利用遞歸調用
{
subdivide(ruler,min,max,i);//當level=0時會從調用函數中返回
std::cout<<ruler<<std::endl;//輸出數組中個元素的值 分次
for(int j=1;j<len-2;j++)
ruler[j]=' ';
}
return 0;
}
void subdivide(char ar[],int low,int high,int level)//遞歸函數設置數組值
{
if(level==0)
return;
int mid=(high+low)/2;
ar[mid]='|';
subdivide(ar,low,mid,level-1);//左邊的數組 注意level的值是一直變化的
subdivide(ar,mid,high,level-1);//右邊的數組
}
使用level來控制遞歸層,函數調用自身時,將把level減1,當level爲0時,該函數將再也不調用本身。 最初的中點被用做一次調用的右端點和另外一次調用的左端點。
調用次數呈幾何級增加,調用一次致使兩個調用而後致使4個,而後8個 最後64個調用。
7.10函數指針
函數地址是存儲機器語言代碼的內存的開始地址
A.基礎知識
一個函數以另外一個函數做爲參數 此時就會須要函數的地址
獲取函數的地址
聲明一個函數指針
使用函數指針來調用該函數
1.獲取函數的地址
函數名即爲函數的地址
有think()函數
例如:process(think) 傳遞函數的地址 做爲參數
process(think()) 傳遞函數think()的返回值作爲參數
2.聲明函數指針
聲明指針必須指定指針的類型。指向函數,也必須指定指針指向的函數的類型
聲明須要指定函數的返回類型以及函數的參數列表
首先聲明被傳遞的函數的原型
double pam(int);//prototype
double (*pf)(int); pf爲指向函數的指針
提示:一般 用*pf替換原來函數原型中的函數名 就會獲得函數的指針
上例中爲*pf替換了pam
注意運算符號的優先級:
double (*pf)(int) 指向函數的指針
double *pf(int ) pf是一個返回指針的函數
正確的聲明以後, 就能夠將函數的地址賦給指針
pf=pam 注意聲明中指針與原來的函數原型返回值和參數列表必須徹底一致
做爲參數傳遞
void estimate(int lines,double(*pf)(int)); 函數原型
estimate(50,pam); 調用
3.使用指針來調用函數
由於*pf等同於pam 因此直接使用*pf做爲函數名便可
double x=pam(4); 用原函數調用
double y=(*pf)(5); 用指針調用
double y=pf(5); 此種方式也能夠 僅僅在c++11 中
B 函數指針示例
//7.18 fun_prt.cpp
#include <iostream>
double betsy(int);//prototype
double pam(int);
void estimate(int lines,double (*pf)(int));
int main()
{
using namespace std;
int code;
cout<<"how many lines of code do you need?";
cin>>code;
cout<<"here's betsy's estimate:\n";
estimate(code,betsy);// 調用betsy函數
cout<<"here's pam's estimate:\n";
estimate(code,pam);//調用pam函數
return 0;
}
double betsy(int lns)
{
return 0.05*lns;
}
double pam(int lns)
{
return 0.03*lns+0.0004*lns*lns;
}
void estimate(int lines,double(*pf)(int))
{
using namespace std;
cout<<lines<<"lines will take";
cout<<(*pf)(lines)<<"hour(s)\n";
}
C 深刻探討函數指針
一下的聲明徹底相同
const double * f1(const double ar[],int n);//以數組的方式聲明 ar[] 與指針*ar 做爲參數時同樣都是指針
const double *f2(const double [],int );省略標識符
const double *f4(const double *ar,int n);
const double *f3(const double *,int);指針形式
假設要聲明一個指針,指向函數,若是指針名爲pa 則只需將目標函數原型中的函數名替換爲(*pa)便可
const double *(*pa)(const double *,int); pa=f3;
const double *(*pa)(const double *,int)=f3 同時初始化 等同與上一句
auto pa2=f1;使用c++11中的auto 自動類型推斷功能 簡化聲明
示例:
cout<<(*pa)(av,3)<<":"<<*(*pa)(av,3)<<endl;
cout<<pa(av,3)<<":"<<*pa(av,3)<<endl;
f3返回值的類型爲const double * 因此前面的輸出都爲函數的返回值 指向const double的指針 即地址值
然後面的輸出均加上了* 解引用運算符 因此實際輸出爲指針指向的double值
D 函數指針數組
const doubel * (*pa[3])(const double *,int)={f1,f2,f3};
括號外爲 一個函數類型
(*pa[3])括號內部分表示一個包含3個指針元素的數組
綜合到一塊兒就是3個指針指向3個不一樣的函數
pa是一個數組
這裏可否使用auto呢?答案是不能,自動類型推斷只能用於單值初始化,而不能用於初始化列表;可是聲明數組pa後,再聲明一樣類型的數組就能夠用auto了
auto pb=pa;
數組名爲指向數組中的一個元素的指針,因此pa和pb都是指向函數指針的指針
pa[i]表示數組中的元素即指針元素,
const double * px=pa[0](av,3); 等同於f1(av,3) 即函數返回值
const double * py=(*pb[1])(av,3); 等同於f2(av,3)
若是想獲得指向的double的值 則須要再加上解引用運算符
const double * px=*pa[0](av,3);
const double * py=*(*pb[1])(av,3);
建立指向整個數組的指針,數組名pa表示指向函數指針(數組元素)的指針,因此指向數組的指針就是指向指針的指針,能夠對其進行初始化,由於這是單個值。因此可使用auto
auto pc=&pa;
也能夠本身聲明 須要再某個位置再加上* 操做符
用*pd來替換原型中的pa
const double *(*(* pd)[3])(const double*,int)=&pa;
pd指向數組,*pd就是數組,而(*pd)[i]是數組中的元素,即函數指針。
因此可使用 (*pd)[i](av,3)來調用函數。前面加上*爲指針指向的double的值
注意pa和&pa的區別,前者指向數組中的的一個元素,即&pa[0],僅僅是單個指針(元素)的地址;後者&pa是整個數組的地址
雖然數值都相同,可是類型是不一樣的。
**(&pa)==pa[0] *pa==pa[0]
//arfupt.cpp
#include <iostream>
const double * f1(const double ar[],int n);
const double * f2(const double [],int n);
const double * f3(const double *,int n);
int main()
{
using namespace std;
double av[3]={111.1,222.2,333.3};
//定義指向函數的指針
const double * (*p1)(const double *,int )=f1;//p1=f1;
auto p2=f2;//自動推斷 c++中
//const double * (*p2)(const double [],int)=f2 等同於上句
cout<<"using pointers to functions:\n";
cout<<"address value\n";
cout<<(*p1)(av,3)<<":"<<*(*p1)(av,3)<<endl;//調用f1 現實double值
cout<<p2(av,3)<<":"<<*p2(av,3)<<endl;
const double *(*pa[3])(const double *,int)={f1,f2,f3}; //指向3個指針的數組 數組名爲pa
auto pb=pa; //pb指向數組中的第一個元素
cout<<"\nusing an array of pointers to functions:\n";
cout<<"address value\n";
for(int i=0;i<3;i++)//循環調用指針數組中的各個函數 而且輸出地址和指向的double值 利用數組名來調用
cout<<pa[i](av,3)<<":"<<*pa[i](av,3)<<endl; //函數指針數組
for(int i=0;i<3;i++)
cout<<pb[i](av,3)<<":"<<*pb[i](av,3)<<endl;//指向一個函數指針的指針 也便是指向一個指針數組中元素的指針
cout<<"\nusing pointers to an array of pointers:\n";
cout<<"address value\n";
auto pc=&pa;//指向指針數組的指針
//等價於const double *(*(*pc)[3] )(const double *,int)=&pa;
cout<<(*pc)[0](av,3)<<":"<<*(*pc)[0](av,3)<<endl;
//本身聲明pd
const double *(*(*pd)[3])(const double *,int)=&pa;
const double *pdb=(*pd)[1](av,3);
cout<<pdb<<":"<<*pdb<<endl;//輸出地址和指向的double的值
cout<<(*(*pd)[2])(av,3)<<":"<<*(*(*pd)[2])(av,3)<<endl;//注意*的位置的不一樣輸出的值也不一樣,前者爲地址,後者爲double值
return 0;
}
const double *f1(const double * ar,int n)
{
return ar;
}
const double *f2(const double ar[],int n)
{
return ar+1;
}
const double *f3(const double ar[],int n)
{
return ar+2;
}
類的虛方法實現一般都採用了這種技術 :指向函數指針數組的指針
7.10.4 使用typedef進行簡化
除了auto外 關鍵字typedef能夠建立類型別名
typedef double real; real爲double的別名
示例:
typedef const double *(*p_fun)(const double *,int); //p_fun now a type name 類型爲const double * (const double *,int)函數類型
p_fun p1=f1;//p1 points to the f1() function
再利用這個別名來簡化代碼
p_fun pa[3]={f1,f2,f3}; pa爲數組 數組中元素類型爲 p_fun
p_fun (*pd)[3]=&pa; pd爲一個指向包含3個元素的數組的指針 此數組元素的類型爲p_fun
第八章 函數探幽
一.內聯函數
不一樣與普通的函數調用 ,內聯函數相似於c語言中的宏 在調用的位置展開函數的代碼
是否使用內聯函數須要對程序運算效率和資源佔用率進行評估,若是普通調用的時間長,且不在意內存的佔用,可使用內聯函數;而若是在意內存的佔用率,不在乎運行時間,則僅僅用普通的函數調用;
普通調用須要cpu跳到內存中存儲函數的位置執行語句;而內聯函數僅僅是在相應的調用函數位置替換成函數的實現代碼,僅僅在內存空間上會增大,而不會進行cpu執行指令的跳躍。
聲明的方法 須要在函數的原型中進行聲明 而且在函數定義前也要加上inline
一般的作法是將整個函數的定義替換函數的原型
示例:
inline double square(double x) {return x*x;}
內聯函數和常規函數同樣 也能夠按值來傳遞參數 若是參數爲表達式,則計算表達式的值,再傳遞
內聯與宏的區別
c 中的宏:#define SQUARE(X) X*X 經過文本替換來實現
b=SQUARE(4.5+7.5); 被替換爲 b=4.5+7.5*4.5+7.5 結果不正確
內聯就不會出現這樣的問題 先計算值 再傳遞
二 引用變量
爲另外一個變量的別名 相似與指針 可是其實是指針的另外一個接口 與指針又不徹底相同
1.建立
int a;
int &b=a;
b=1;
結果b=1 a=1
int a=101;
int & b=a;
int *c=&a;
表達式b和*c 與a能夠互換,而表達式&b 和c與&a能夠互換,前者爲值,後者爲地址
注意引用必須在聲明時初始化 而指針能夠先聲明,再賦值
int & b;
b=a;這種作法是錯誤的
在這一點上,引用與const 指針 是相似的,一旦與某個變量關聯起來 就一直是他,不可改變
int & b=a;其實是int * const c=&a;的假裝表示
示例:
int a=1;
int &b=a;
int c=2;
b=c;
實際上 b仍然是a的引用,可是b=c執行的是a=c=2,由於b是a的引用,因此b的值也是2
2.2將引用做爲函數的參數
引用傳遞參數能夠容許被調用函數可以訪問調用函數中的變量 相似與指針的操做。
void swapr(int &a,int &b) 按引用 改變的是調用函數中的變量
void swapv(int a,int b) 按值傳遞 生成臨時變量,不改變原值
2.3 引用的屬性和特別之處
若是程序員想使用調用函數中的變量,可是又不想修改
則須要加入const 限定符
double refcube(const double &ra);
當程序試圖改變ra的值時, 編譯器將生成錯誤消息
按值傳遞的函數,可使用多種類型的實參:
double z=cube(x+2.0) 是合法的
可是引用限制比較嚴格 引用爲變量名的引用 :
double z=refcube(x+3.0); 不合法 由於不能存在這樣的表達式:x+3.0=5.0
臨時變量 引用參數和const
若是實參與引用參數不匹配,c++將生成臨時變量。
僅當參數爲const引用時,纔會生成臨時變量。
建立臨時變量的時間
A 實參的類型正確,但不是左值
B 實參的類型不正確,可是能夠轉換成正確的類型
左值是能夠被引用的數據對象 實際上就是表明內存空間 包括const和非const兩種左值 可變和不可變
非左值包括字面常量和包含多項的表達式
示例:
double refcube(const double &ra)
{
return ra*ra*ra;
}
double side=3.0;
double * pd=&side;
double & rd=side;
long edge=5L;
double lens[4]={2.0,5.0,10.0,12.0};
double c1=refcube(side); ra is side
double c2=refcube(lens[2]); ra is 10.0
double c3=refcube(rd); ra is rd is side
double c4=refcube(*pd); ra is * pd is side
double c5=refcube(edge); ra is edge 類型不一樣 可是能夠轉換成double 因此產生臨時變量
double c6=refcube(7.0); 產生臨時變量 類型正確 可是不是左值
double c7=refcube(side+10.0); 產生臨時變量 類型正確 可是不是左值
生成的臨時匿名變量 讓ra指向它,這些臨時變量只在函數調用期間存在,此後編譯器即可以隨意將其刪除
儘量使用const
使用const 能夠避免無心中修改數據的編程錯誤;
使用const使函數可以處理const和非const實參,不然將只能接受非const數據
使用const引用使函數可以正確生成並使用臨時變量
c++11新增長的右值引用 使用&&來聲明
double && rref=std::sqrt(36.00) 用來指向右值
double && jref=2.0*j+18.5;
& 爲左值引用 &&爲右值引用
將引用用於結構
struct free_throws
{
std::string name;
int made;
int attempts;
float percent;
}
原型的寫法:void set_pc(free_throws & ft); 在函數中將指向該結構的引用做爲參數
若是不但願函數修改傳入的結構,可以使用const
void display(const free_throws & ft);
8.3
//strc_ref.cpp
#include <iostream>
#include <string>
struct free_throws
{
std::string name;
int made;
int attempts;
float percent;
};
//prototype
void display(const free_throws & ft); //const 引用
void set_pc(free_throws & ft); //非const 引用 結構
free_throws & accumulate(free_throws & target,const free_throws & source); // 返回指向結構的引用
int main()
{
free_throws one={"ifelsa branch",13,14};//初始化結構體
free_throws two={"andor knott",10,16};
free_throws three={"minnie max",7,9};
free_throws four={"whily looper",5,9};
free_throws five={"long long",6,14};
free_throws team={"throwgoods",0,0};//臨時
free_throws dup;//臨時2
set_pc(one);
display(one);
accumulate(team,one); //返回指向結構的引用
display(team);//顯示指向結構的引用
display(accumulate(team,two));//顯示 accumulate函數返回的結構的引用做爲參數
accumulate(accumulate(team,three),four);//嵌套函數 參數爲函數返回的結構的引用 team可寫
display(team);
dup=accumulate(team,five);//函數返回做爲右值
std::cout<<"Displaying team:\n";
display(team);
std::cout<<"displaying dup after assignment:\n";
display(dup);
set_pc(four);
accumulate(dup,five)=four;//函數返回值做爲左值 函數返回 爲一個結構體的引用
std::cout<<"displaying dup after ill-advised assignment:\n";
display(dup);
return 0;
}
void display(const free_throws & ft)
{
using std::cout;
cout<<"name:"<<ft.name<<'\n';
cout<<"made:"<<ft.made<<'\n';
cout<<"attempts:"<<ft.attempts<<'\n';
cout<<"percent:"<<ft.percent<<'\n';
}
void set_pc(free_throws & ft)
{
if(ft.attempts!=0)
ft.percent=100.0f*float(ft.made)/float(ft.attempts);
else
ft.percent=0;
}
free_throws & accumulate(free_throws & target,const free_throws & source)//返回引用 指向結構
{
target.attempts+=source.attempts;
target.made+=source.made;
set_pc(target);
return target;
}
程序說明:
set_pcp(&one); 指針的方式調用 傳遞的是地址
void set_pcp(free_throws * pt) { pt->attempts=...} 實現 須要使用指針做爲參數來接傳過來的地址
以上兩句的搭配一樣能夠實現引用的功能,即修改傳過來的實參
display(accumulate(team,two));
與下面等效:accumulate(team,two);display(team);
爲什麼要返回引用
傳統返回機制與按值傳遞函數的參數相似
計算return 後面的表達式,並將結果返回給調用函數,可是過程是,計算的結果會被複制到一個臨時的位置,而調用程序將使用這個值。
double m=sqrt(16.0); 將4.0複製到一個臨時的位置 再被賦值給m
cout<<sqrt(25.0); 5.0被複制到一個臨時的位置, 再傳遞給cout
dup=accumulate(team,five); 若是accumulate()返回一個結構,而不是指向結構的引用,將把整個結構複製到一個臨時的位置,再將這個拷貝複製給dup
可是返回值做爲引用時,將把team複製到dup 效率更加高效
返回引用時須要注意的問題
避免返回函數終止時再也不存在的內存單元的引用
案例:
const a & fun(a & ft)
{
a newguy; //函數內部的變量
newguy=ft;
return newguy;
}
該函數返回一個指向臨時變量 newguy的引用,函數運行完畢後將再也不存在。一樣的也應該避免返回指向臨時變量的指針
解決辦法1:返回一個指向實參的引用,由於實參不在函數內部定義,所以,能夠返回 實參變量不消失
辦法2:使用new來分配新的存儲空間,使用new爲字符串分配內存空間,並返回指向該內存空間的指針。
案例:
const free_throws & clone(free_throws & ft)
{
free_throws * pt;
*pt=ft;
return * pt;
}
爲什麼將const用於引用返回類型
accumulate(dup,five)=four; accumulate 返回類型爲非const類型 此語句有效
將five的數據存儲到dup中,再使用four的內容覆蓋dup的內容。 左側爲必須可修改的左值。左邊的子表達式必須標識一個可修改的內存塊。
常規(非引用)返回類型是右值-不能經過地址訪問的值。這種表達式能夠出如今賦值語句的右邊,但不能出如今左邊。例如 10.0 或者向x+y表達式 都是右值,獲取10.0的地址沒有意義,常規函數返回值是右值的緣由就是這種返回值位於臨時內存單元中,運行到下一跳語句時,他們就再也不存在了。
若是將accumulate聲明爲const free_throws & accumulate();則accumulate(dup,five)=four 此語句不成立 由於等號右邊爲不可修改的左值
將引用用於類對象
將類對象傳遞給函數時,一般使用引用,例如能夠經過使用引用,讓函數將類string 、 ostream、istream、ofstream和ifstream 等類的對象做爲參數。
示例:
#include <iostream>
#include <string>
using namespace std;
string version1(const string & s1,const string & s2);
const string & version2(string & s1,const string & s2);
const string & version3(string & s1,const string & s2);
int main()
{
string input;
string copy;
string result;
cout<<"enter a string : ";
getline(cin,input);\\input接收string對象的字符串
copy=input;\\string賦值
result=version1(input,"****");
result=version2(input,"####");
result=version3(input,"@@@");
return 0;
}
string version1(const string & s1,const string & s2)
{
string temp;
temp=s2+s1+s2;//鏈接字符串
reurn temp;//按值返回 程序正常 temp在函數結束後,不存在,因此返回前 會將temp變量的內容複製到一個臨時的存儲的單元中,而後再將此值賦值給result
}
cosnt string & version2(string & s1,const string & s2)
{
s1=s2+s1+s2;
return s1;//按引用返回 程序正常
}
cosnt string & version3(string & s1,const string & s2) //程序崩潰的緣由爲試圖引用已經釋放的內存
{
string temp;
temp=s2+s1+s2;
return temp;//返回函數內部的臨時變量的引用 程序崩潰 因temp在函數執行完後 它所表明的內存區域被取消
}
將c風格字符串用做string對象引用參數
對於函數version1 形參爲const string & ,實參分別爲string和const char * 因爲input的類型爲string,s1指向input是沒有問題的。可是const string & 引用的類型接收了實參爲char * 的字符指針類型 第2個參數
解釋:必定具備轉換的功能 可使用c風格字符串來初始化string對象。涉及到形參爲const引用時的規則 產生臨時變量。與實參的類型不匹配可是能夠正確轉換。
一樣也能夠將實參char * 或者const char *傳遞給形參const string &。
這種屬性的結果是:若是形參類型爲const string & ,在調用函數時,使用的實參能夠是string對象或c風格字符串。例如引號括起來的字符串字面量、以空字符結尾的char數組或者指向char 的指針變量 都是可行的
對象 、繼承和引用
ostream是基類 (控制檯輸入出) ofstream是派生類 (文件輸入出)
派生類繼承了基類的方法 意味着ofstream對象可使用基類的特性,如格式化方法 precision()和sett()
基類引用能夠指向派生類對象 而無需進行強制類型轉換
致使的結果就是形參爲基類引用 實參能夠是基類對象,也能夠是派生類對象
ostream & 爲形參,能夠接受ostream對象或者本身聲明的ofstream對象做爲參數
示例:能夠經過同一個函數 (只有函數調用參數不一樣) 將數據寫入文件和顯示到屏幕上來講明
#include <iostream>
#include <fstream>
#include <cstdlib>
void file_it(ostream & os,double fo,cosnt double fe[],int n); //文件 prototype
const int LIMIT=5;
int main()
{
osftream fout;//對象
const char * fn="ep-data.txt";//指定文件
fout.open(fn);//關聯文件
if(!fout.is_open())//判斷文件是否關聯成功
{
cout<<"can't open"<<fn<<".bye.\n";
exit(EXIT_FAILURE);
}
double objective;
cout<<".....";
double eps[LIMIT];
for(int i=0;i<LIMIT;I++)
{
cout<<"asdfasdf";
cin>>eps[i];
}
file_it(fout,objective,eps,LIMIT);//輸出到文件
file_it(cout,objective,eps,LIMIT);//輸出到標準輸出
cout<<"done\n";
return 0;
}
//實現
void file_it(ostream & os, double fo, const double fe[], int n)//其中os能夠指向ostream對象 (cout) 也能夠指向ofstream對象 (fout)
{
ios_base::fmtflags initial;
initial=os.setf(ios_base::fixed);//備份
os.precision(0); 點後0位
os<<"asdfasdf"<<fo<<"mm\n";
os.setf(ios::showpoint);
os.precision(1); 點後1位
os.width(12);//設置字符寬度
os<<"f.l. eyepiece";
os.width(15);
os<<"magnification"<<endl;
for(int i=0;i<n;i++)
{
os.width(12);
os<<fe[i];
os.width(15);
os<<int(fo/fe[i]+0.5)<<endl;
}
os.setf(initial); //還原默認顯示格式
}
每一個對象都存儲了本身的格式化設置 設置完成以後再恢復爲默認的設置
什麼時候使用引用參數:
使用引用參數的主要緣由:
程序員可以修改調用函數中的數據對象(傳遞過來的實參)
提升程序運行的速度
實參不作修改:
數據對象小,內置類型 按值傳遞
數據對象是數組,使用指針,指針聲明爲指向const的指針
大結構 使用const指針或const 引用
類對象 const引用
實參須要修改:
內置類型 使用指針
數組 只能使用指針
結構 指針或者引用
類對象 引用
默認參數
將默認真賦值給原型中的參數
char * left(const char *str,int n=1); 原型 中從右向左開始按順序賦初值 不容許跳躍
char harpo(int n,int m=4,int j=5);成立
調用:
left(sample,4); 不使用默認值
或者 left(sample); 使用默認值
空值字符的編碼爲0,循環將結束。
函數重載
也叫函數多態,讓您可以使用多個同名的函數
函數名相同,可是參數列表不一樣 也叫函數特徵標不一樣,
什麼叫作函數特徵標相同:兩個函數的參數數目 和 類型相同,同時參數的 排列順序也相同,則它們的特徵標相同。與變量名是無關的
示例:
原型:
void print(const char * str,int width); #1
void print(double d,int width); #2
void print(long l,int width); #3
void print(const char * str); #4
調用:
print("pancakes",15); 對應#1
print("syrup"); 對應#4
print(year,6); 沒有匹配,可是並不會自動中止使用其中的某個函數,由於c++嘗試使用標準類型轉換強制進行匹配。若是原型僅#2 則c++會強制轉換爲#2,可是若是有多個原型符合,例如#2 和#3都適合 則就會發生錯誤 編譯器不知道轉換到那個函數
參數若是爲類型的引用和類型 則編譯器將視爲是一個特徵標
即double cube(double x); 和 double cube(double & x); 不是重載函數
另外一個:const和非const變量
示例:
void dribble(char * bits); #1 重載
void dribble(const char * cbits); #2 重載
void dabble(char *bits); #3
void drivel(const char * bits); #4
const char p1[20]="how's the weather?";
char p2[20]="how's business?";
dribble(p1); 2
dribble(p2); 1
dabble(p1); 沒有匹配的 const類型 沒有匹配的
dabble(p2); 3
drivel(p1); 4
drivel(p2); 4 能夠 將非const 傳遞給const形參
將非const值賦值給const變量是合法的,但反之則是非法的。
參數列表 特徵標 而不是函數類型使得能夠對函數進行重載。
long gronk(int n,float m); //這兩個函數不是重載的函數
double gronk(int n,float m); 因此聲明兩次是不容許的
函數名相同可是參數列表不一樣 返回類型能夠不一樣
重載引用函數 類設計和STL常用引用參數
void sink(double & r1); //參數爲可修改的左值
void sank(const double & r2); //參數爲可修改或不可修改的左值 或者右值
void sunk(double && r3); //右值
假設 重載使用這三種參數的哦函數,結果須要調用最匹配的函數
void staff(double & rs); 可修改左值 1
void staff(const double & rcs); 右值 或者常量左值 2
void stove(double & r1); 可修改左值 3
void stove(const double & r2); 常量左值 4
void stove(double && r3); 右值 5
調用:
double x=55.5;
const double y=32.0;
stove(x); 3
stove(y); 4
stove(x+y); 5 若是5沒有定義,則stove(x+y) 將調用函數stove(const double &)
案例:
8.10 leftover.cpp
略
8.4.2 什麼時候使用函數重載
函數重載僅當函數基本上執行相同的任務,但使用不一樣形式的數據時,才應採用函數重載。
注意默認參數的函數和函數重載之間的區別,若是須要使用不一樣類型的參數,則默認參數便不能使用了 必需要用到函數重載。
小貼士: 名稱修飾
概念:也叫名稱矯正 給重載函數指定祕密的身份
功能:用於c++的跟蹤 每一個重載函數
原理:編譯器根據函數原型中指定的形參類型對每一個函數名進行加密 以進行跟蹤
例子: 源代碼
long My FunctionFoo(int ,float);
轉變爲:?MyFunctionFooYAXH 此行根據函數的特徵標不一樣而不一樣 用於編譯器跟蹤
函數模板
函數模板爲通用的函數描述,使用泛型來定義函數 其中的泛型可使用具體的類型來替換
經過類型做爲參數傳遞給模板,可以使編譯器生成該類型的函數。
示例:
template <typename AnyType> 紅色部分爲必需的部分,能夠用class(c++98標準)代替typename 必須使用尖括號 AnyType爲類型名
void Swap(AnyType &a,AnyType &b)
{
AnyType temp;
temp=a;
a=b;
b=temp;
}
一般用T來代替AnyType
案例:
#include <iostream>
template <typename T>
void Swap(T &a,T &b)
int main()
{
using namespace std;
int i,j;
...
Swap(i,j); //此時會生成int版本的函數代碼
double x,y;
Swap(x,y); //此時生成double版本的函數代碼
return 0;
}
template <typename T>
void Swap(T &a,T &b)
{
T temp;
temp=a;
a=b;
b=temp;
}
重載的模板
須要多個對不一樣類型使用同一種算法的函數時,可以使用模板。
並不是全部的類型都使用相同的算法。 此時就須要針對不一樣的函數特徵標,來定義不一樣的模板函數 即 重載模板 參數列表不一樣
案例:
#include <iostream>
template <typename T> # 1
void Swap(T &a,T &b)
template <typename T> # 2
void Swap(T *a,T *b,int n);//注意此時最後一個參數是int類型的, 而不是泛型, 因此說並不是全部的模板參數都必須是模板參數類型
int main() //模板的重載
{
int i,j;
...
Swap(i,j);// 調用1
int d1[8]={0,7,0,4,1,7,7,6};
int d2[8]={0,7,2,0,1,9,6,9};
show(d1);
show(d2);
Swap(d1,d2,8);// 調用2
return 0;
}
template <typename T> #1
void Swap(T &a,T &b)
{
T temp;
temp=a;
a=b;
b=temp;
}
template <typename T> # 2
void Swap(T a[ ],T b[ ],int n)
{
T temp;
for(int i=0;i<n;i++)
{
temp=a[i];
a[i]=b[i];
b[i]=temp;
}
}
void show(){ }
模板的侷限性
template <class T>
void f(T a,T b)
{
a=b; //此時若是T爲數組 此語句不能執行
if(a>b) //此時若是T爲結構體 則>和< 運算符不能執行
T c=a*b //此時若是T爲數組、指針或者結構 此語句也不能執行
}
所以經過上例能夠看出 模板函數有時沒法處理特殊的類型 一方面須要通用化,另外一方面又須要爲特定類型提供具體化的模板定義。
顯示具體化
概念:提供的一個具體化的函數定義--稱爲顯示具體化 其中包含所需的代碼。當編譯器找到與函數調用匹配的具體化定義時,將使用該定義,而再也不尋找模板。
示例:
struct job
{
char name[40];
double salary;
int floor;
}
模板函數 swap() {} 的功能是交換兩個job結構中的三個成員值 使其互換
可是若是僅僅想交換salary和floor兩個成員變量的值時,模板函數沒法提供相應的功能,此時就須要顯示具體化的函數定義
1.第三代具體化 iso/ansi c++標準
分類:給定的函數名 能夠有非模板函數、模板函數和顯示具體化模板函數以及它們的重載版本
顯示具體化的原型和定義應該以 template < > 開頭 ,並經過名稱來指出類型
具體化優先於常規模板,而非模板函數優先於具體化和常規模板 也就是優先級 = 非模板 > 顯示具體化函數 > 常規模板函數
函數原型的定義示例:
void swap(job &,job &); 常規函數 即非模板函數 #1
template <typename T> 模板函數 #2
void swap(T &,T &);
template < > void swap <job> (job & ,job &); 顯示具體化函數 #3
//template <> 函數名 <函數類型>(參數列表);
template <class T> 模板函數
void swap(T &,T &);
template <> void swap<job>(job &,job &); 顯示具體化 其中<job> 中的job是可選的,由於函數的參數類型代表 這是job的一個具體化 (括號中已經指明瞭是job類型的參數) 因此也能夠這樣寫template <> void swap(job &,job &); 這種簡單的格式
int main()
{
double u,v;
...
swap(u,v); 使用模板函數
job a,b;
...
swap(a,b);使用顯示具體化
}
顯示具體化示例:
//twoswap.cpp
#include <iostream>
template <typename T>//函數模板
void Swap(T &a,T &b);
struct job
{
char name[40];
double salary;
int floor;
};
template <> void Swap<job>(job &j1,job &j2);//顯示具體化
void show(job &a);
int main()
{
using namespace std;
cout.precision(2);
cout.setf(ios::fixed,ios::floatfield); //格式化設置
int i=10;
int j=20;
cout<<"i,j="<<i<<","<<j<<".\n";
cout<<"using compiler-generated int swapper:\n";
Swap(i,j); //此時調用模板
cout<<"now i,j="<<i<<","<<j<<".\n";
//下面爲結構體
job sue={"susan yaffee",73000.60,7};
job sidney={"sidney taffee",78060.72,9};
cout<<"before job swapping:\n";
show(sue);
show(sidney);
Swap(sue,sidney);//此時調用具體化
cout<<"after job swapping:\n";
show(sue);
show(sidney);
return 0;
}
template <typename T> //模板實現
void Swap(T &a,T &b)
{
T temp;
temp=a;
a=b;
b=temp;
}
template <> void Swap<job>(job &j1,job &j2) //具體化實現
{
double t1;
int t2;
t1=j1.salary;
j1.salary=j2.salary;
j2.salary=t1;
t2=j1.floor;
j1.floor=j2.floor;
j2.floor=t2;
}
void show(job &j)
{
using namespace std;
cout<<j.name<<":$"<<j.salary<<"on floor"<<j.floor<<endl;
}
注意上例中的Swap中的S若是替換爲小寫的s 會出現call of overload is ambiguous 調用重載函數不明確 緣由就是在std名稱空間中還有一個swap函數 重複定義 因此解決辦法爲函數名不一樣或者在swap()前加上::
即 ::swap( )
實例化和具體化
實例化與類和對象相似 模板至關於類 函數調用時至關於實例化 的對象
1.函數模板不是函數定義 僅僅是生成函數定義的方案
2.編譯器使用模板爲特定類型生成函數定義,獲得函數實例化 例如 int i,j; swap(i,j); 獲得一個swap函數的實例。此實例使用int類型
3.模板不是函數定義,可是使用int類型的模板的實例就是一個函數定義
以上是隱式實例化的例子 編譯器經過程序中給swap模板函數提供了int類型 編譯器才知道如何實例,而且進行定義。
隱式實例化時編譯器自動運行生成的實例
而c++ 編譯器能夠容許顯式的實例化 也就是能夠直接用命令 使得編譯器建立特定的實例,例如swap<int> ( )
語法爲:聲明所需的類型 用<>符號指示類型 並在聲明前加上關鍵字 template
template void swap<int>(int ,int); 這是顯式的實例化
template <> void swap<int>(int &,int &); 這是顯式具體化1 聲明
tmeplate <> void swap(int &,int &); 顯式具體化2 聲明
具體化1和具體化2是等價的
具體化12聲明的意思是:不要使用swap()模板來生成函數定義,而應使用專門爲int類型顯式地定義的函數定義(使用具體化的定義)
注意實例化和具體化的區別是:在template後,具體化聲明有<>,而實例化沒有<>;
試圖在同一個文件(或轉換單元)中使用同一種類型的顯式實例和顯式具體化將出錯
顯式實例化案例:
template <class T> //普通模板
T Add(T a,T b)
{
return a+b;
}
...
int m=6;
double x=10.2;
cout<<Add<double>(x,m)<<endl; 顯式實例化 直接用命令調用編譯器經過模板來實例化函數
x和m 不匹配,由於該模板要求兩個函數參數的類型相同。可是顯示實例化中的命令Add<double>(x,m) 能夠強制爲double類型實例化,並將參數m強制轉換爲double類型,以匹配模板函數
若是將swap函數也顯式實例化的命令 swap<double> (m,x) 這些代碼無用,由於第一個形參的類型爲double & ,不能指向int變量m 沒法強制轉換
隱式實例化(編譯器默認行爲)、顯示實例化和顯式具體化統稱爲具體化。
相同之處: 表示的都是使用具體類型的函數定義,而不是通用描述(通用模板)
顯式具體化和顯式實例化的語法是不一樣的 template後面受否跟着<>
示例:
template <class T>
void swap(T &,T &); //通用模板 1
template <> void swap<job>(job &,job &); //顯式具體化 聲明 2
int main(void)
{
template void swap<char>(char &,char &); //顯式實例化 for char類型 編譯器看到後就會使用模板定義來生成swap的char版本 3
short a,b;
swap(a,b); 調用通用模板 生成short版本的函數 1
job n,m;
swap(n,m); 調用顯式具體化 生成job版本的函數 2
char g,h;
swap(g,h); 調用顯式實例化生成的模板具體化的函數定義 3
}
編譯器選擇使用哪一個函數版本 */**********
對於函數重載、函數模板和函數模板重載等,c++編譯器須要有一個良好的策略來決定函數實現時應該調用哪個函數的定義,尤爲是有多個參數。
上面的過程叫作重載解析。
大體的過程:
1.建立候選函數列表,其中包含與被調用函數的名稱相同的函數和模板函數
2.使用候選函數列表建立可行函數列表。
這些都是參數數目正確的函數,爲此有一個隱式轉換序列,其中包括實參類型與相應的形參類型徹底匹配的狀況。
例如 使用float參數的函數調用能夠將該參數轉換爲double 從而與double形參匹配,而模板能夠爲float生成一個實例。
3.肯定是否有最佳的可行函數。若是有,則使用它,不然該函數調用出錯
案例:
函數:may('B'); 參數的類型是一個字符類型 函數調用
首先編譯器找候選者,即名稱爲may()的函數和函數模板。
再次尋找一個參數的函數
下面的函數:
void may(int); #1 不須要轉換
float may(float,float=3); #2 默認參數可行
void may(char); #3 隱式轉換爲char
char * may(const char *); #4 整數不能被隱式地轉換爲指針類型 因此此函數不可能被調用
char may(const char &); #5 常量引用 能夠接收整型的變量
template <class T> void may(const T &); #6 常量引用
template <class T> void may(T *); #7 整數不能被隱式地轉換爲指針類型 因此此函數不可能被調用
注意只考慮特徵標,而不考慮返回類型。其中的兩個候選竄函數#4和#7不可行,由於整數類型不能被隱式地轉換爲指針類型。剩餘的一個模板可用來生成具體化,其中T被替換爲char類型。
#1---#7中 4和7不能被調用。
而後編譯器須要肯定哪一個可行函數是最佳的。 它查看爲使函數調用參數與可行的候選函數的參數匹配所須要進行的轉換。一般優先級以下:
1.徹底匹配 常規函數優先於模板函數
2.提高轉換 例如 char和shorts 自動轉換爲int ;float自動轉換爲double
3.標準轉換 例如int轉換爲char long轉換爲double
4.用戶定義的轉換,如類聲明中定義的轉換
#1優於#2 char到int的轉換時提高 char到float是標準轉換
# 3 5 6 都優於#1 2 由於它們徹底匹配
#3 和#5優於#6 由於6是函數模板
剩下3和5 如何區分呢?
對徹底匹配有以下的討論
1.徹底匹配和最佳匹配
徹底匹配時,c++容許某些 可有可無的轉換
type能夠是常規的類型 也能夠是相似char &的類型
type 意味着用做實參的函數名與用做形參的函數指針只要返回類型和參數列表相同,就是匹配的。
struct blot{int a ;char b[10];};
blot ink={25,"spots"};
recyle(ink);
下列的原型都是徹底匹配的: 實 形參
void recycle(blot); blot-to-blot
void recycle(const blot); blot-to-(const blot)
void recycle(blot &); blot-to-(blot &)
void recycle(const blot &); blot-to-(const blot &)
若是有多個匹配的原型 則編譯器沒法解析過程;若是沒有最佳的可行函數,則編譯器將生成一條錯誤消息,該消息可能會使用諸如「ambiguous(二義性)」這樣的詞語。
即便兩個函數徹底匹配 也能夠完成重載解析的狀況:
指向非const數據的指針和引用優先與非const指針和引用參數匹配。
略