C++17入門經(jīng)典上
Chapter 1 基本概念
- 頭文件包含許多內(nèi)容,其中包括.cpp文件中的可執(zhí)行代碼使用的函數(shù)原型,以及使用的類和模板的定義
- 預處理指令會以某種方式修改源代碼,之后會把他們編譯為可執(zhí)行的形式
- 頭文件的內(nèi)容會添加到源文件中
- 頭文件的內(nèi)容被插入到#include指令的位置
- 流是數(shù)據(jù)源或數(shù)據(jù)接收器的一種抽象表示,每個流都關(guān)聯(lián)著某臺設(shè)備,關(guān)聯(lián)著數(shù)據(jù)源的流就是輸入流,關(guān)聯(lián)著目的地的流就是輸出流
- 名稱空間類似于姓氏
- 兩個冒號::有一個非常奇特的名稱,作用域解析運算符
- main函數(shù)不能定義在名稱空間中,未在名稱空間中定義的內(nèi)容都存在于全局名稱空間中,全局名稱空間沒有名稱
- 不要使用以下劃線開頭的名稱
- 有時候程序需要幾個類似的類或函數(shù),其代碼中只有所處理的數(shù)據(jù)類型有區(qū)別,編譯器使用模板給特定的自定義類型自動生成類或函數(shù)代碼
Chapter 2 基本數(shù)據(jù)類型
- 花括號稱為初始化列表,初始化列表可以包含幾個值;優(yōu)勢:允許以相同的方式初始化所有變量,被稱為統(tǒng)一初始化
- 可以在花括號中使用單個值來初始化任何變量 c++17
int apple_count{12};
int counter{};//零初始化 {}相當于0
const unsigned toe_count{10};//常量
- const 可以固定任何類型的變量值,不可被修改
- unsigned 是無符號數(shù),永遠不會是負值
- 整數(shù)除法返回的是分母除以分子得到的倍數(shù),任何余數(shù)都會被舍棄
- 浮點類型數(shù)據(jù)不能使用unsigned或signed修飾符,浮點類型總是帶符號的
- cmath頭文件中的所有函數(shù)可接受任意浮點類型或整型參數(shù)
- abs(arg)
- ceil(arg)
- floor(arg)
- exp(arg)
- sqrt(arg)
- round(arg)
- pow(arg1,arg2)
- tan(angle)
- iomanip頭文件,控制數(shù)據(jù)格式
- fixed 用小數(shù)點固定格式
- dec 十進制格式
- left 左對齊格式
- setprecision(n) 一共n位輸出,if有fixed,則表示小數(shù)點后n位數(shù)字
- setw(n) 輸出序列寬度
- setfill(ch) ch填充多余,默認是空格
- static_cast<要轉(zhuǎn)的新類型>(數(shù)據(jù)) 強制類型轉(zhuǎn)換
- auto 關(guān)鍵字可以告訴編譯器應推斷數(shù)據(jù)類型
auto num{10};
Chapter 3 處理基本數(shù)據(jù)類型
- 枚舉 定義:
enum class 類名:指定成員的類型{枚舉成員1,...,枚舉成員n};//默認枚舉成員1 = 0,后一個比前一個大1
// 成員類型可以省略不寫
可以當成類來看待,就是一個類名數(shù)據(jù)類型,引用枚舉時,必須使用類型名(類名)來限定他
枚舉成員的值必須是編譯器可以計算出來的常量表達式,這種表達式包括字面量,以前定義的枚舉成員,聲明為const的變量,不能使用非const變量(即使使用字面量初始化也不行)
enum class Punctuation : char{Comma=',',Exclamation='!',Question='?'};
- using關(guān)鍵字允許把自己的數(shù)據(jù)類型名稱指定為另一個類型的替代名稱
using BigOnes = unsigned long long;
- 全局變量的初始化在main()開始之前進行,默認情況下被初始化為0.局部變量將全局變量隱藏時,使用作用域解析運算符(::)來限定它
Chapter 4 決策
-
cctype頭文件
- isupper(c) c是否是大寫字母
- isalpha(c) c是否是字母
- isdigit(c) c是否是數(shù)字
- isspace(c) c是否是空白,\n \t \r \f ' '
- isblank(c) c是否是空格 ‘ ’ \t‘
- tolower(c) 返回c的小寫
- isalnum(c) if c 包含字母或數(shù)字,就返回一個正整數(shù)(true)else 0(false)
if(變量){} == if(變量!=0) == if(變量!=false) //內(nèi)! 外==
if(!變量){} == if(變量==0) == if(變量==false)
Chapter 5 數(shù)組和循環(huán)
數(shù)組
- 數(shù)組的大小必須用常量表達式來指定
- 數(shù)組沒有初始化,所以包含的都是垃圾值
double temp[100];
temp[3] = 99.0;
unsigned int height[6];
unsigned int height1[6]{1,2,3,4,5,6};
int height3[6] {};// all 0
const int height4[6]{1,2,3,4,5,6};
- size_t 是某個不帶符號的整數(shù)類型的別名,足夠大,能夠容納編譯器支持的任何類型(包括數(shù)組)
- 幻數(shù):產(chǎn)量值的多次使用直接定義為const
- 數(shù)組大小一般設(shè)置為unsigned,一般不會為負值
- std::size(array); 獲取數(shù)組的大小,c++17
- c++可以使用浮點數(shù)來控制for循環(huán)
//無限循環(huán)中,輸入指定個數(shù)元素
x[count] = input;
if(++count == size){
cout<<"無法容納"<<size<<"個元素"<<endl;
break;
}
- 無符號數(shù)減去值時候應該小心,0-1=numice_limits<size>::max()
- 字符數(shù)組初始化字符串字面量,默認會加上 ’\0‘
char name{"aeiou"};
//6 elements
//因為字符串的最后添加了'\0'來標記字符串結(jié)束,所以數(shù)組包含6個元素
- 使用數(shù)組名不能輸出數(shù)值類型的數(shù)組的內(nèi)容,這種方法僅僅用于輸出char數(shù)組,即使是傳送給輸出流的char數(shù)組,也必須用空字符結(jié)束,否則程序很可能崩潰
const int maxLength{100};
char text[maxLength]{}
for(int i{};text[i]!='\0';i++){//一個字符數(shù)組中的原因個數(shù)
if(isalpha(text[i])){
switch(tolower(text[i])){
case 'a':case 'e':case 'i': case 'o' :case 'u':
++vowels;
break;
default:
++consonants;
}
}
}
- 不能使用cin>>直接讀取輸入內(nèi)容,因為>>不能碰到空格
- cin.getline(text,maxlength);
- 多維數(shù)組和一維的類似,空初始化列表是將其初始化為0
- 多維數(shù)組除了第一個緯度之外,必須指定大小,編譯器只能推斷第一個緯度的大小
- 在運行期間給數(shù)組分配內(nèi)存空間
cin>>count;
unsigned int height[count];
//此時數(shù)組無法初始化,因為不知道有多少元素。
//因此這種帶有變量個數(shù)的數(shù)組,只能先聲明,后賦值,不可直接初始化
數(shù)組的替代品(優(yōu)于數(shù)組)
std::array
array<T,N>
include<array>
- if 創(chuàng)建array<>容器但不指定初始值,則數(shù)組也包含垃圾值
- fill()函數(shù)把所有元素設(shè)置為某個給定的值
array<double,100> values{};
values.fill(3.1415);// all elements to pi
- array.size(); 返回數(shù)組的元素個數(shù)size_t類型,array對象總是可以通過size()函數(shù)確定自己的大小
- array.at(i); 返回i出的值,i是一個索引
- array.front();返回數(shù)組第一個值
- array.back();返回數(shù)組最后一個值
- 兩個array只要容器大小相同,存儲類型相同,可以使用== != > < 對兩個array中的元素進行逐個比對,也可相互賦值
std::vector<T>
- 大小可自動增加,可容納任意數(shù)量元素
- push_back(X);向vector中添加元素
vector<long> numbers(20,99L);//理解為構(gòu)造器(),20個 99
vector<int> number(20);//有20個0
vector<long> number{20};//只有一個20
vector<unsigned int> primes{1,2,3,4,4,4};//初始化{}列表
- 與array類似,兩個vector也可以比較,但是vector可以元素數(shù)量不同的比,字典序比較
- 一個vector給另外一個vector賦值時,被賦值的會被覆蓋已經(jīng)存在的值
- vector可以存儲在其他容器中
- vector沒有fill()成員,但是提供了assign函數(shù),可用于重新初始化vector<>內(nèi)容
vector<long> numbers(20,99L);//20個99
numbers.assign(99,20L);//99個20
numbers.assign({99L,20L});//99 和 20
numbers.clear();//清空numbers
numbers.empty()//vector中是空,沒有數(shù)據(jù)嗎?返回true orfalse
numbers.pop_back();//刪除vector最后一個元素
如果編譯時知道元素的準確數(shù)量,就是用array<>,如果不知道就是用vector<>
Chapter 6 指針和引用
指針
- 指針是可存儲地址的變量。他指向內(nèi)存中存儲了其他值的位置
- 初始化列表為空,所以這個語句把pnumber初始化為等價于0的指針,即不指向任何內(nèi)容的地址,等價于0的指針寫為nullptr
long* pnumber{nullptr};
long *pnumber{nullptr};
- 定義指針時,總是要初始化他,如果還不能為指針提供期望的值,就將其初始化為nullptr
- 最好在單獨的代碼行上聲明指針和普通變量,以避免出現(xiàn)這種混淆。
- 不管指針指向什么類型或大小的數(shù)據(jù),指針變量本身的大小始終是相同的,如今的平臺上指針變量大小一般是4或8字節(jié)
地址運算符&
- &是一個一元運算符,他可以獲取變量的地址。
- &運算符可以應用于任何類型的變量,但必須在對應類型的指針中存儲地址
- 使用編譯器推斷指針類型,但是盡量還是使用auto*
auto* number{&height};
- 使用auto*聲明的變量只能用指針值初始化,使用其他類型的值初始化,會導致編譯錯誤。
間接運算符
- 將間接運算符*應用于指針,可以訪問指針所指向的內(nèi)存位置的數(shù)據(jù),也成為為解引用運算符
int count{};
int* pount{&count};
- *是乘法運算符,間接運算符,還可以應用于聲明指針,一般編譯器根據(jù)上下文分析
- 使用指針的用處
- 動態(tài)的為新變量分配內(nèi)存空間,即可以在程序執(zhí)行過程中分配
- 使用指針表示法操作存儲在數(shù)組中的數(shù)據(jù),與普通數(shù)組表示法完全等效
- 指針可以在函數(shù)中訪問函數(shù)外部定義的大塊數(shù)據(jù)
- 指針是支持多態(tài)性起作用的基礎(chǔ)
char類型的指針
- 指向char類型的變量,可以用字符串字面量(實際上是const char類型的數(shù)組)初始化
- 給未解除引用的char類型指針應用<<插入運算符-----假定這種指針包含以空字符結(jié)尾的字符串的地址
- 如果給解除引用的char類型指針應用<<插入運算符---將地址中的單個字符寫入cout
const char* pproverb{"A miss is as good as a mail"};
void proj1()
{
const char* pstr1{ "fatty arb" };
const char* pstr2{ "clara bow" };
const char* pstr3{ "lassie" };
const char* pstr{ "your lucky star is " };
cout << "pick a lucky star! enter a number between 1 and 3:" << endl;
size_t choice{};
cin >> choice;
switch (choice)
{
case 1:cout << pstr << pstr1 << endl; break;//your lucky star is fatty arb
case 2:cout << pstr << pstr2 << endl; break;//字符串名字直接輸出字符串
case 3:cout << pstr << pstr3 << endl; break;//未解除引用
default:cout << "sorry you haven't got a lucky star." << endl;
}
cout << *pstr1 << endl;//輸出首字符(首地址) 解除引用
}
- 指針數(shù)組
每個因子都是一個const char* ,一個const char* 指向一個字符串字面量
其中的*pstr[i] 無法進行再賦值,因為其為常量const,無法進行修改
void proj2()
{
const char* pstars[] = {//默認都會加上\0
"fatty","clara","lassie","slim","boris","mae","oliver","greta"
};
//pstars[]數(shù)組中的每個元素 會指向一個const char類型的變量(串/字符數(shù)組)
cout << "pick a lucky star! enter a number between 1 and " << size(pstars) << endl;
int choice{};
cin >> choice;
if (choice>0 &&choice<size(pstars))
{
cout << "your lucky star is " << pstars[choice - 1] << endl;
}
else
{
cout << "sorry!" << endl;
}
pstars[0] = pstars[1];//數(shù)組內(nèi)部可以相互賦值,就很離譜!!
}//指針數(shù)組法
把一個聲明從右向左讀。
char * const cp; ( * 讀成 pointer to指向 )
cp is a const pointer to char
const char * p;
p is a pointer to const char;
const char* my_favorite_star{ "Lassie" };
my_favorite_star = "Mae";//my_favorite_star本身不是const變量, my_favorite_star = pstars[1];
- 指向常量的指針
指針指向的內(nèi)容不能修改,但可以把指針設(shè)置為指向其他內(nèi)容
const char* pstring{"some text that cannot be changed"};
const int value{20};
const int* pvalue{&value};
int a{ 1 };
pvalue = &a;
value是一個常量,不能修改,pvalue是一個指向常量的指針,可以用于存儲value的地址。不能在非const int指針中存儲value的地址。
但是可以把非const變量賦給pvalue、
- 常量指針
存儲在指針中的地址不能修改。
只能指向初始化時指定的地址。
但是地址的內(nèi)容不是常量,可以修改,
int data{ 20 };
int* const pdata{ &data };
*pdata = 25;//內(nèi)容可變
- 指向常量的常量指針
因為存儲在指針中的地址和指針指向的內(nèi)容都被聲明為常量,所以兩者都不能修改
const float value{3.14};
const float* const pvalue{&value};
指針和數(shù)組
- 數(shù)組名可以像指針那樣操作,輸出時,使用非char類型的數(shù)組名,就可以得到內(nèi)存中的地址。
int num[]{ 1,2,3 };
cout << num << endl;
-
指針可進行的運算:
-
加減:(在數(shù)組中表現(xiàn)為向后/前移動一位,就是一個類型的字節(jié)數(shù))
-
指針+整數(shù)=指針
int data[]{1,2,4}; int* pdata{&data[1]}; cout<<* (pdata+1)<<endl;//4 指針-整數(shù)=指針
指針+-指針=整數(shù)(類型相同,同一數(shù)組中)
結(jié)果是兩個索引的差值
-
比較
-
動態(tài)內(nèi)存分配
- 所有指針都應該初始化,如果指針沒有包含合法的地址,就應該總讓他包含nullptr
double* pvalue{new double{}};//初始化為0.0
double* pnull{};//初始化為nullptr
delete pvalue;
pvalue = nullptr;
delete 只是將自由存儲區(qū)的內(nèi)存釋放,變量還可以使用,但是最好先將其置為nullptr
與普通數(shù)組不同的是,無法讓編譯器推斷出動態(tài)分配數(shù)組的維數(shù)
double* data{new double[100]};//100個垃圾值
int* num{new int[100]{0}};//100個0
int* one{new int[]{1,2,3}};//報錯,無法推斷
delete[] data;//釋放動態(tài)數(shù)組的內(nèi)存
data=nullptr;
- 釋放動態(tài)數(shù)組的內(nèi)存,使用delete[] 或者delete [] 方括號表示刪除的數(shù)組
- ->指針選擇成員
- 多維動態(tài)數(shù)組?
void proj6()
{
int rows{ 3 };
int columns{ 3 };
//動態(tài)二維數(shù)組
//carrots數(shù)組是double*指針的一個動態(tài)數(shù)組,每個double*指針包含一個double數(shù)組的地址
double** carrots{ new double* [rows]{} };
for (size_t i = 0; i < rows; i++)
{
carrots[i] = new double[columns] {};
}
for (size_t i = 0; i < rows; i++)
{
delete[] carrots[i];
}
delete[] carrots;
}
- 每個new必須對應一個delete,每個new[] 必須對應一個delete[]
- 在c++程序設(shè)計中,盡量不要使用new delete new[] delete[],應該使用vector<> 和智能指針來規(guī)避動態(tài)內(nèi)存的一些風險。盡量避免直接操作動態(tài)內(nèi)存。
智能指針
不必使用delete delete[] 運算符釋放內(nèi)存
智能指針不能進行遞增或遞減,也不能進行算術(shù)操作
<memory>頭文件
-
三種智能指針
-
unique_ptr<T>
- 這個對象類似于指向T類型的指針,是惟一的。
- 從不會有兩個unique_ptr<>對象指向同一地址
- 指向的值被該對象獨占
unique_ptr<double> pdata{ new double{999.0} }; cout << pdata << endl;//地址 cout << *pdata << endl;//數(shù)值 cout << pdata.get() << endl;//get()函數(shù)返回地址 unique_ptr<double> pdata1{ make_unique<double>(999.0) }; auto pdata2{ make_unique<double>(1.9) };// //三個方法相同,推薦最后一種寫法,簡潔,且可以防止內(nèi)存泄漏reset() 將指針重置為nullptr,如果參數(shù)不設(shè)置值,就是nullptr
-
release() 將智能指針轉(zhuǎn)換為普通指針,注意,將其轉(zhuǎn)化為原始指針之前一定要先保存原始指針再進行釋放,否則將出現(xiàn)內(nèi)存泄漏的情況
const size_t n{ 100 }; unique_ptr<double[]> pvalues{ new double[n] }; auto pvalues1{ new double[n] }; auto pvalues2{ make_unique<double[]>(n) };//動態(tài)創(chuàng)建n個元素的數(shù)組 cout << pvalues1 << endl;//地址 for (size_t i = 0; i < n; i++) { pvalues2[i] = i + 1; } /*for (size_t i = 0; i < n; i++) { cout << pvalues2[i] << endl; }*/ pvalues.reset();//將指針重置為nullptr double* values = pvalues2.release(); delete values;
-
-
shared_ptr<T>
-
創(chuàng)建shared_ptr<T> 過程更復雜一些,主要是需要維護引用計數(shù),
void proj8() { shared_ptr<double> pdata{ new double{111.1} }; shared_ptr<double> pdata2;//初始化為nullptr pdata2 = pdata; cout << *pdata2 << endl; }- 復制pdata會增加引用次數(shù),兩個指針必須重置或釋放,double變量占用的內(nèi)存才會釋放
- 實際使用共享指針的情況通常涉及對象
-
weak_ptr<T>
理解引用
- 引用就是一個別名,可以用作某對象的別名,不可以只聲明引用而不對其初始化
- 引用不能修改另一個另一個對象的別名
- 類型名后面&符號表示引用,如果取得引用的地址,結(jié)果會是指向原始變量的一個指針
就當做別名去理解,想不清楚就用原名
void proj9()
{
double data{ 3.5 };
double& rdata{ data };
double* pdata1{ &rdata };
double* pdata2{ &data };
cout << (pdata1 == pdata2) << endl;//相等
double* pdata{ &data };
*pdata += 2.5;
double other_data{ 5.0 };
rdata = other_data;
cout << rdata << endl;
}
Chapter 7 操作字符串
-
c++的string頭文件定義了string類型,相比ctring頭文件中以‘\0’結(jié)尾的c字符串更可靠
-
定義string對象的六種方式
- empty
- 字面量
- 字面量切割
- 構(gòu)造器
- string對象
- string對象切割
string empty;//長度為0,不包含字符的字符串empty
string proverb{"many a mickle make a muckle."};//字面量
string part_literal{"least said soonest mended.",5};//least 字面量切割
string sleeping(6,'a');//6個a 構(gòu)造器
string sentence{proverb}; //string 對象
string sentence{proverb,0,13};//(begin,num) //string對象的切割
-
c串與string串的轉(zhuǎn)化,(string 字符串的長度不會計算\0,但是有\(zhòng)0存在(不理它))
- string.c_str();
- string.data();
string proverb{"many a mickle make a muckle."};//字面量
const char* proverb_c_str = proverb.c_str();//const char* 類型字符串
//const char* 表示里面的字符是不能變化的,但是proverb_c_str可以變,指別的地方
char* prover_data = proverb.data();//不是const
cout<<proverb.length();
-
string對象的操作
- 賦值
- 字面量賦值
- 串變量賦值
string adj{"hornwogging"};//字面量賦值 string word{"ribbish"}; word{adj};//串變量賦值 adj = "twotiming";- 連接
- +連接:必須有一個string對象在+的一側(cè)
- string.append(str,begin,end);連接
- string.append(str,'字符');
string word{"this is a string object"} string description{"whipppersnapper"+word}; string compliment{"~~~what a beautiful name...~~~"}; sentence.append(complient,3,22);//what a beautiful time sentence.append(,'!');//!!!- 數(shù)串連接:
? to_string(數(shù)字)+字符串
double num{ 100.0 }; string name{ "I'm Huang Hongwei.I am " }; cout << name + to_string(num) + " years old" << endl; - 賦值
-
讀入串getline(cin,text)
string text{}; getline(cin, text); cout << text << endl; -
訪問子串
str.substr(be,num)
str.substr(be)
str.substr()//父子相同
-
out_of_range類型
string phrase{ "The higher the fewer." }; string word1{ phrase.substr(4,6) }; cout << word1 << endl; string word2{ phrase.substr(4,100) }; cout << word2 << endl; string word{ phrase.substr(4) }; cout << word << endl; string str{ phrase.substr() }; cout << str << endl;
-
比較字符串
-
字典序比較:
- 前面的字符都相同,看長度,越長越大
- 長度相同,且對應字符相同,則相同
string對象可以存儲到容器中,普通的char數(shù)組不能存儲到容器中。std空間定義了一個非成員函數(shù)模板,實現(xiàn)效果與swap(a,b)相同
-
compare()函數(shù)
-
obj. compare()函數(shù)可以比較該對象
for(size_t i{1};i<names.size();++i) { if(names[i-1].compare(names[i]>0)) { names[i].swap(names[i-1]); sorted = false; } }
-
使用substr()進行比較
string text{ "peter piper picked a peck of pickled pepper. " }; string phrase{ "got to pick a pocket or two." }; for (size_t i = 0; i < text.length()-3; i++) { if (text.substr(i,4)==phrase.substr(7,4)) { cout << "text contains " << phrase.substr(7, 4) << " starting at index " << i << endl; } }?
-
-
搜索字符串find()
string sentence{ "manners maketh man" };
string word{ "man" };
cout << sentence.find(word) << endl;
cout << sentence.find("ma")<< endl;
cout << sentence.find("k")<< endl;
cout << sentence.find("x")<< endl;//返回string:npos
- find(串,對象的初始查找點)
cout<< sentence.find("an",1);//從1開始查找,結(jié)果是1
cout<<sentence.finde("an",3);//從3開始查找,結(jié)果是16,第二次出現(xiàn)an的位置
-
搜索任意字符集合find_first_of() find_last_of()
? 從頭開始 從結(jié)尾開始
string text{"hjkld,ddddd ddddd ddd ddd \"}; string operators{",.\""}; cout<<text.find_first_of(operators)<<endl;//返回集合中(有一個匹配就返回)第一個出現(xiàn)的位置 5 -
find_first_not_of() find_last_not_off()
搜索不在字符集合中的字符的位置
cout<<text.find_first_not_of("aeiouAEIOU");//查找第一個不是元音的位置 逆向搜索字符串str.rfind();
string sentence{"manners maketh man"};
string word{"an"};
cout<<sentence.rfind(word);//16 從后向前搜索,index=16是an第一次出現(xiàn)的地方
//從n開始搜索
-
修改字符串
-
插入 :索引位置前插入,充當當前索引insert()
string phrase{ "we can insert a string." }; string words{ "a string into " }; //phrase.insert(14, words);//在14位置插入words cout << phrase << endl; phrase.insert(13, words, 8,5); //words中從8開始的5個字符 插入到對象的13位置 cout << phrase << endl;
-
-
替換
string text{ "we can replace a string" }; text.replace(1, 5, "123456");//在index=1的地方,的5 個字符,替換為123456 cout << text << endl;
-
刪除
刪除[begin,]之后的len個字符
unsigned begin{ text.find('c') };
int len{ 3 };
cout << text.erase(begin,len) << endl;
將字符串轉(zhuǎn)化為整型
stoi(string)
string s{"12334"};
int i{stoi(s)};
Chapter 8 定義函數(shù)
返回類型 函數(shù)名 (參數(shù)列表)
{
}
- 函數(shù)調(diào)用中的實參順序必須對應于函數(shù)列表里的參數(shù)順序。函數(shù)名和參數(shù)列表的組合稱為函數(shù)的簽名。
- 函數(shù)體中可以有多個return語句,每個return語句可能返回不同的值。
- 返回類型是void->return ;
- 函數(shù)原型=函數(shù)聲明:定義了函數(shù)名,函數(shù)的返回值,參數(shù)列表;
- 聲明一定要放在調(diào)用之前。。除非把實現(xiàn)寫在寫在引用之前。(同c語言)
給函數(shù)傳遞實參
如果指定的函數(shù)實參類型不對應參數(shù)類型,編譯器就會把參數(shù)的類型隱式轉(zhuǎn)換為參數(shù)類型
按值傳送:
實參的變量值或常量值根本不會傳送給函數(shù),而是創(chuàng)建實參的副本,把這些副本傳給函數(shù)。執(zhí)行完函數(shù)之后就廢棄副本(不會對原有的值進行更改)
- 給函數(shù)傳遞指針:參數(shù)為指針類型時,按值傳送機制就會像以前那樣運行,但是指針包含另一個變量的地址,此時,指針的副本也包含這樣一個副本
#include<iostream>
#include<string>
#include<iomanip>
using namespace std;
double changeIt(double* pointer_to_it);
int main()
{
double it{ 15.0 };
double result{ changeIt(&it) };
cout << "After function execution, it = " << it << "\n Result returned is " << result << endl;
return 0;
}
double changeIt(double* pit)
{
*pit += 10.0;
cout << "within function, *pit = " << *pit << endl;
return *pit;
}
給函數(shù)傳送數(shù)組:
給函數(shù)傳送數(shù)組的地址要比傳送數(shù)組更高效,(不需要復制許多元素),函數(shù)體中的代碼可以把表示數(shù)組的參數(shù)作為指針來看待,即函數(shù)體中可以給數(shù)組參數(shù)使用指針表示法的強大功能
void proj2()
{
double values[]{ 1.0,2.0,3.00,4.0,5.0,6.0,7.0,8.0,9.0,10.0 };
cout << "average = " << average(values, size(values)) << endl;
}
double average(double array[], size_t count)
{
double sum{};
for (size_t i = 0; i < count; i++)
{
sum += array[i];
}
return sum / count;
}
注意事項:
不能通過size()來避免指定count值,數(shù)組參數(shù)array只是存儲數(shù)組的地址,并不是數(shù)組本身;
如果使用sizeof(array) 將返回數(shù)組地址的內(nèi)存位置的大小,而不是整個數(shù)組的大小
可以相互使用數(shù)組和指針表示法
double average2(double* array,size_t count)
{
double sum{};
for (size_t i = 0; i < count; i++)
{
sum += *array++; // 效果一樣
//sum+= array[i];
}
return sum / count;
}
存在誤區(qū):不要向函數(shù)傳遞固定大小的數(shù)組
如果只想計算10個數(shù)的平均值,
double average10(double array[10])
{
double sum{};
for (size_t i = 0; i < 10; i++)
{
sum += array[i];
}
return sum / 10;
}
這個函數(shù)的簽名啊雖然合法,但是呢,會給人一種錯誤的期待
編譯器會強制將剛好包含10個元素的數(shù)組作為實參傳遞給該函數(shù)
double average10(double array[10])
double average10(double array[])
double average10(double* array)
//等價的三種方式
//指定維數(shù)的數(shù)組如果本例中只輸入一個帶有三個元素的數(shù)組,那么仍然會使用10個去計算,下標會越界
const 指針參數(shù)
只需要訪問數(shù)組的值,而不需要修改,使用 *const 類型 arrray ** 只讀指針
double average(const double* array,size_t count){}
指定指針為const有兩個結(jié)果,
- 編譯器檢查函數(shù)體中的代碼,確保不會試圖修改指針所指向的值(讀指針)
- 允許用指向一個常量的實參來調(diào)用函數(shù)
把多維數(shù)組傳給函數(shù)
函數(shù)原型:
double yield(const double values[][4],size_t n);
最好不要在原型的第一個緯度上寫數(shù)字,可以通過編譯器自動推斷,但是后續(xù)的緯度必須寫
double yield(const double array[][4], size_t size);
void proj3()
{
double beans[3][4]{
{1.0,2.0,3.0,4.0},
{5.0,6.0,7.0,8.0},
{9.0,10.0,11.0,12.0} };
cout << "yield = " << yield(beans, size(beans)) << endl;
}
//多維數(shù)組并不適合使用指針表示法
double yield(const double array[][4], size_t size)
{
double sum{};
for (size_t i = 0; i < size; i++)
{
for (size_t j = 0; j < std::size(array[i]); j++)
{
sum += array[i][j];
}
}
return sum;
}
note:一層數(shù)組不要使用size(),編譯器無法推斷 a[][num]的第一個維數(shù),因為形參只是保存了數(shù)組的地址,并不知道數(shù)組的緯度情況
按引用傳送
對應與引用參數(shù)的實參不會復制,引用參數(shù)用實參初始化,它是調(diào)用函數(shù)中該實參的別名
eg: 引用string string&
類型&
對于類類型這種,按值傳送實參和包含因用的函數(shù)傳參是沒有區(qū)別的,但是按值傳送會對值進行復制,造成內(nèi)存的浪費
-
對比引用與指針
- 最明顯的區(qū)別:傳送指針的時候,先使用&獲取一個值的地址,而在函數(shù)內(nèi)需要使用*解引用
- 指針的鮮明特點:可以為nullptr,而引用必須為某個值。因此允許實參為null,就不能使用引用
- 使用引用可以寫出更優(yōu)雅的語法,但是if不看原型,分辨不清是否應用了引用,(和按值傳送的時候長的一模一樣)
- 一般認為const int& 比 const int *d更好用
在使用指針之前必須測試指針是否為nullptr,而引用一般不需要擔心
-
對比輸入輸出參數(shù)
note: 一般不要使用 即作為輸入?yún)?shù),又作為輸出參數(shù)的變量
- 輸入?yún)?shù):一般兩個選擇:const引用傳送:const string&(建議使用) 按值傳送:string (會復制string對象)
- 可以將T值傳送給T&和constT&引用,但是只能將const T 值傳送給const T&引用(當然其他也可以傳送給他,比如T 表示別名)
-
按引用傳送數(shù)組
優(yōu)點:不進行復制。能夠修改原始值
可以通過傳遞引用向函數(shù)傳遞精確的多維數(shù)組一維的個數(shù)
double average10(const double (&array)[10]); //此時可以在函數(shù)中使用size()note: 關(guān)于引用的隱式轉(zhuǎn)化問題:
會有一段臨時空間實現(xiàn)相互轉(zhuǎn)換,但是double->int時會出現(xiàn)丟失,所以會有報錯
void double_it(const double& it) {//針對于這種帶const的會涉及底層轉(zhuǎn)換 cout<<it<endl; } int age{19}; double_it(age);//報錯
新的const string引用
c++17 #include<string_view>
string_view() 具有常量特性,他只需要指向某個實際對象:string 字符串字面量或其他任何字符數(shù)組中存儲的字符序列,,初始化和復制string_view 的開銷都特別低
-
隱式創(chuàng)建string會復制字符,但是創(chuàng)建string_view則不會
string_view sv{"hjkl"}; string_view 不允許直接將其轉(zhuǎn)化為string,因為底層涉及char[]的轉(zhuǎn)化,必須進行強制類型轉(zhuǎn)化
string_view 的幾點注意事項:
- 不完全等同于const string ,沒有提供c_str()來轉(zhuǎn)化為一個const char* 的數(shù)組。但是提供了data()函數(shù),于此功能等效
- 不能使用+運算符連接string_view 可以先轉(zhuǎn)化成string再連接eg: string{view};
- string_view 可以從c樣式的字符數(shù)組創(chuàng)建,大小可以任意大
默認實參值
可在函數(shù)原型中指定默認實參值,但是要按照優(yōu)先級進行指定
void proj4()
{
showError();
}
void showError(string messages)
{
cout << messages << endl;
}
從函數(shù)中的返回值
可以從函數(shù)中返回任意類型的值。主要討論返回指針和引用中的陷阱
返回指針
在指針返回到調(diào)用函數(shù)的時,指針指向的變量必須仍然在其作用域中。
警告:不要從函數(shù)返回在棧上分配的自動局部變量的地址
- 不在調(diào)用程序中的地址
int* larger(int a, int b)
{
//這些地址不在調(diào)用程序中,所以都是錯誤的
if (a > b)
{
return &a;//wrong!
}
else
{
return &b;//wrong!
}
}
- 在調(diào)用程序中的地址
int* larger(int* a, int* b)//傳來了地址由指針接收
{
if (*a > *b)
{
return a;//來源于調(diào)用程序的地址返回,是可以的
}
else
{
return b;//ok
}
}
一個案例,求一組數(shù)據(jù)的標準化
在.0-1.0之間的一組數(shù)
步驟:
-
減去最小值(查找最小值,每個數(shù)字 - 最小值)
//查找最小值 const double* smallest(const double data[], size_t count)//不會對最小值進行更改,讀指針 {//我只是用這個值而已 if (count==0) { return nullptr; } size_t index_min{}; for (size_t i = 1; i < count; i++) { if (data[index_min]>data[i]) { index_min = i; } } return &data[index_min]; } //使用最小值,調(diào)整數(shù)組 double* shift_range(double data[], size_t count, double delta) { for (size_t i = 0; i < count; i++) { data[i] += delta; } return data; } -
每個元素除以max可以將數(shù)組映射到0-1(找到max,除以max)
//查找最小值 const double* largest(const double data[], size_t count)//不會對最小值進行更改,讀指針 { if (count == 0) { return nullptr; } size_t index_max{}; for (size_t i = 1; i < count; i++) { if (data[index_max] < data[i]) { index_max = i; } } return &data[index_max]; } //除以最大值 double* scale_range(double data[], size_t count, double divisor) { if (divisor == 0) { return data; } for (size_t i = 0; i < count; i++) { data[i] /= divisor; } return data; }
void show_data(const double data[], size_t count = 1, string title = "data values", size_t width = 10, size_t perline = 5);
double* normalize_range(double data[], size_t count);
const double* largest(const double data[], size_t count);//不會對最小值進行更改,讀指針
const double* smallest(const double data[], size_t count);//不會對最小值進行更改,讀指針
double* scale_range(double data[], size_t count, double divisor);
double* shift_range(double data[], size_t count, double delta);
int* larger(int a, int b);
int main()
{
//proj1();
proj2();
return 0;
}
void proj2()
{
double samples[]{
11.0,23.0,13.0,4.0,
57.0,36.0,317.0,88.0,
9.0,100.,121.0,12.0 };
const size_t count{ size(samples) };
show_data(samples,count,"original values");
normalize_range(samples, count);
show_data(samples, count, "normalized values", 12);
}
void show_data(const double data[], size_t count, string title, size_t width, size_t perline)
{
cout << title << endl;
for (size_t i = 0; i < count; i++)
{
cout << setw(width) << data[i];
if ((i+1)%perline==0)
{
cout << '\n';
}
}
cout << endl;
}
const double* largest(const double data[], size_t count);
const double* smallest(const double data[], size_t count);
double* shift_range(double data[], size_t count, double delta);
double* scale_range(double data[], size_t count, double divisior);
double* normalize_range(double data[], size_t count);
//規(guī)范化
double* normalize_range(double data[], size_t count)
{
return scale_range(shift_range(data, count, -(*smallest(data, count))), count, *largest(data, count));
}
//查找最小值
const double* largest(const double data[], size_t count)//不會對最小值進行更改,讀指針
{
if (count == 0)
{
return nullptr;
}
size_t index_max{};
for (size_t i = 1; i < count; i++)
{
if (data[index_max] < data[i])
{
index_max = i;
}
}
return &data[index_max];
}
//除以最大值
double* scale_range(double data[], size_t count, double divisor)
{
if (divisor == 0)
{
return data;
}
for (size_t i = 0; i < count; i++)
{
data[i] /= divisor;
}
return data;
}
//查找最小值
const double* smallest(const double data[], size_t count)//不會對最小值進行更改,讀指針
{
if (count==0)
{
return nullptr;
}
size_t index_min{};
for (size_t i = 1; i < count; i++)
{
if (data[index_min]>data[i])
{
index_min = i;
}
}
return &data[index_min];
}
//使用最小值,調(diào)整數(shù)組
double* shift_range(double data[], size_t count, double delta)
{
for (size_t i = 0; i < count; i++)
{
data[i] += delta;
}
return data;
}
返回引用
不要從函數(shù)中返回自動局部變量的引用
想要返回對某個實參的非const引用,就不能把參數(shù)指定為const
參數(shù)不是const,所以不能把字符串字面量用作實參,編譯器不允許這么做
在使用類創(chuàng)建自己的數(shù)據(jù)類型時,引用返回類型是必不可少的
返回類型的推斷
類型推斷可以節(jié)省時間:return 之后的數(shù)據(jù)類型要明確
auto 不會推斷為一個引用類型,而總是推斷為一個值的類型,即使將一個引用賦值給auto,值也會被復制。而且值的這個副本不是const,除非使用const auto
要讓編譯器推斷一個引用類型,可以使用auto& 或const auto&
函數(shù)體中的靜態(tài)變量
函數(shù)體中的靜態(tài)變量在第一次執(zhí)行的 時候回初始化,后續(xù)調(diào)用的時候不會在初始化,而只是進行操作,變量始終存在于程序中,直到程序結(jié)束
普通變量聲明帶有垃圾值
靜態(tài)變量初始化會附帶0值
內(nèi)聯(lián)函數(shù)
內(nèi)聯(lián)函數(shù)的定義通常放在頭文件中,該頭文件包含在使用該函數(shù)的每個源文件中。即使用該函數(shù)的每個源文件都應該對其進行引用
inline int larger(int m,int n)
{
return m > n ? m : n ;
}
函數(shù)重載
同名,但是參數(shù)列表不同即可
參數(shù)列表的幾種情況很難判斷
重載和指針參數(shù)
int* 類型處理起來和int[]的參數(shù)類型相同
int* p == int p[];
編譯器會無視指定數(shù)組維數(shù)的方法,如果需要指定維數(shù),可以指定array<>或者按照引用傳遞
重載和引用參數(shù)
如下原型
void do_it(stirng number);
void do_it(string& number);
編譯器表示很懵逼,do_it(124);到底調(diào)用的是誰?
所以不能根據(jù)type1 和type1& 來區(qū)分數(shù)據(jù)類型
note: 臨時地址可以用{}來初始化const,而不能用{}初始化非const
重載和const參數(shù)
帶有const參數(shù)和沒有const的參數(shù)唯一的區(qū)別就是,為引用定義參數(shù)還是為指針定義參數(shù)
-
對于基本類型 int和const int 是相同的
//下面兩個原型沒有區(qū)別 long larger(long a ,long a); long larger(const long a ,const long b);//編譯器會忽略這個const -
重載和const指針參數(shù)
在兩個重載函數(shù)中,一個函數(shù)的參數(shù)是type *(會修改值),另一個函數(shù)的參數(shù)類型是const type *(一個只讀指針,不會修改) 這兩個函數(shù)是不同的
-
編譯器不會把const值傳送給非const指針參數(shù)的函數(shù)
const 值會傳送給const指針
const int* larger(const int* a,const int* b); const int num{0}; *larger(&num) -
一個函數(shù)的參數(shù)類型是“指向type的指針”,另一個函數(shù)的參數(shù)類型是“指向type的const指針”
這兩個函數(shù)就是相同的
long* larger(long* const a,long* const b);//常量a指向long類型(不能指向別的了) const long* larger(const long* const a,const long* const b);//常量a指向常量的long類型 -
重載和const引用參數(shù)
不允許在&后面添加const
T& (const常量無法賦值給T)和const T&(可以接受const和非const)是不同的