參數
什麼是參數?我們買手機,電腦最喜歡看它們的參數,比如CPU是幾核心,頻率多少,內存多大等。廣義 講,參數可以理解成一個東西的屬性的值。在程序中,參數可以看作是一個變量,變量是計算機的專有名詞,變量來源於數學,是計算機語言中能儲存計算結果 或 能表示值 抽象概念。
在程序中,參數有一些分類和特性:形式參數,實參,可變參數,默認參數等。
形參(形式參數)
在函數定義中出現的參數可以看做是一個佔位符,它沒有數據,只能等到函數被調用時接收傳遞進來的數據,所以稱爲形式參數,簡稱形參。
實參(實際參數)
函數被調用時給出的參數包含了實實在在的數據,會被函數內部的代碼使用,所以稱爲實際參數,簡稱實參。
形參和實參的功能是傳遞數據,發生函數調用時,實參的值會傳遞給形參。形參,只有在函數被調用時纔會被分配內存,函數執行完成後立即被釋放內存,所以一般來說形參變量只在函數內部有效。實參,可以是常量、變量、表達式、函數等,無論實參是何種類型的數據,在進行函數調用時,它們都必須有確定的值,以便把這些值傳送給形參,所以應該提前用賦值、輸入等辦法使實參獲得確定值。一般來說,實參和形參在數量上、類型上、順序上必須嚴格一致,否則會發生“類型不匹配”的錯誤。當然,如果能夠進行自動類型轉換,或者進行了強制類型轉換,那麼實參類型也可以不同於形參類型。函數調用中發生的數據傳遞是單向的,只能把實參的值傳遞給形參,而不能把形參的值反向地傳遞給實參;換句話說,一旦完成數據的傳遞,實參和形參就再也沒有瓜葛了,所以,在函數調用過程中,一般,針對普通類型形參的值發生改變並不會影響實參。
默認參數
通常情況下,函數調用時,形參從實參那裏取得值。C++給出了可以不用從實參取值的方法,給形參設置默認值。
//一個簡單的例子
#include <iostream>
#include <string>
using namespace std;
void weatherForcast(string w = "sunny"){
cout<<"today weather: "<<w<<endl;
}
int main()
{
weatherForcast();
weatherForcast("rainny");
return 0;
}
//執行結果
//today weather: sunny
//today weather: rainny
默認參數的聲明順序
默認參數的個數小於等於形參的個數,且默認的順序只能從右到左默認,不能跳躍!
默認參數只能在函數聲明處,默認值可以 常量,全局變量,或是一個 函數。
可變參數
有時我們需要編寫一些在源代碼編寫階段無法確定參數個數,有時甚至無法確定參數類型的函數。比如有時候我們寫多個函數要對同一類型不同函數個數的參數進行計算。如果有一個東西,可以自動幫我們計算出參數的個數,那麼我們就不用重載這麼多相似的函數了。它們可以在運行時取任意的實參個數並根據實參的個數自動處理不同實參的情形,或者至少可以在運行時指定任意的實參個數。的確存在這麼一個東西,具體實現細節請看後續的函數裏面的內容
數組
是內存中連續存儲的 一種 同種數據類型的元素。
//一個最簡單的數組初始化例子
int n[10] = {}; //這種聲明會隱式的將元素初始化爲0,這種方式只能用於數組的聲明。
//數組的幾種堆內存申請操作的寫法
//一維數組
int *ar = new int[100]{0};
int **ap = new int*[5]{NULL};
//二維數組
int (*arr)[6] = new int[5][6];
//三維數組
int (*arrr)[5][6] = new int[3][5][6];
//釋放數組
delete [] ar;
delete [] ap;
delete [] arr;
delete [] arrr;
//申請指針數組
char **rpp = new char*[5];
rpp[0] = "china";
rpp[1] = "sichuan";
rpp[2] = "shenzhen";
rpp[3] = "google";
for(int i = 0;i < 5;i++){
cout<<rpp[i]<<endl;
}
int a,b,c;
int *pArr[] = {&a, &b, &c};
數組名存在的意義?
數組名是數組中首元素的地址,本質是一個常量指針。
C++中數組沒有邊界檢查,邊界檢查可防止計算機引用不存在的元素
將數組傳遞給函數時,應該同時傳遞數組的大小,而不是讓函數確定數組的大小,這可使函數更具有通用性
按引用傳遞數組能提高效率
函數
函數是一組一起執行一個任務的語句。每個 C++ 程序都至少有一個函數,即主函數 main() ,所有簡單的程序都可以定義其他額外的函數。C++中關於函數,可以知道有內置函數(標準庫提供),自定義函數,內聯函數,匿名函數(Lambda 函數),仿函數等。調用函數的方式一般有:傳值,指針,引用三種調用方式。這裏單獨說一點,個人認爲可以參考其它高級語言的一個叫法上的區分:如果一個函數位於一個類中,則應該把這個函數叫做是這個類的方法。
有兩個用法在我們實際工作中經常用到:
函數指針:
包含函數在內存中的地址。函數名實際上是執行這個函數的任務的代碼在內存中的起始地址。函數指針可以傳給函數,從函數返回,保存在數組中,賦予另一個函數指針或調用底層函數。
//用法
int (*f) (int x); /* 聲明一個函數指針 */
f=func; /* 將func函數的首地址賦給指針f */
函數指針數組:
一個用法是出現在菜單驅動系統中。例如:程序可以提示用戶通過輸入一個整數值來選擇菜單中的一個選項。用戶的選擇可以用作函數指針數組的下標,而數組中的指針可以用來調用函數
實現可變參數的函數:
請看作者:https://blog.csdn.net/qq_35280514/article/details/51637920,已經寫的很詳細了,所以不再過多說明。
在C++中實現一個變參函數的方法有三種:第一種方法,將函數形參聲明爲C++11新標準中的initializer_list標準庫類型;第二種方法繼承自C語言,形參聲明爲省略符,函數實現時用參數列表宏訪問參數;最後一種方法利用C++泛型特性,聲明一個可變參數模板來實現。把上述鏈接的作者的例子代碼貼出來:
方式2:(不安全,不推薦)
#include <iostream>
#include <stdarg.h>
using namespace std;
int sum(int count, ...); //count 表示可變參數的個數
int sum(int count, ...){
va_list ap;
va_start(ap, count);
int sum = 0;
for(int i = 0;i < count;i++)
sum += va_arg(ap, int);
va_end(ap);
return sum;
}
int main()
{
int ret = 0;
ret = sum(10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
cout<<ret<<endl; //55
return 0;
}
方式1:
#include <iostream>
#include <initializer_list>
using namespace std;
int sum(initializer_list<int> il);
int sum(initializer_list<int> il){
int sum = 0;
for(auto p = il.begin(); p != il.end(); p++)
sum += *p;
return sum;
}
int main()
{
int ret = 0;
initializer_list<int> ts = {1, 2, 3, 4, 5, 6};
ret = sum(ts);
cout<<ret<<endl; //21
return 0;
}
方式3:
#include <iostream>
#include <initializer_list>
using namespace std;
template <typename T>
std::ostream &print(std::ostream &os, const T &t){
return os << t;
}
template <typename T, typename... Args>
std::ostream &print(std::ostream &os, const T &t, const Args &... rest){
os << t<< ",";
return print(os, rest...);
}
int main()
{
return 0;
}
內聯函數:
C 語言中有宏函數的概念。宏函數的特點是內嵌到調用代碼中去,避免了函數調用的開銷(壓棧出棧)。但是由於宏函數的處理髮生在預處理階段,缺失了語法檢測和有可能帶來的語意差錯。如何讓函數,即有宏函數的快速也有普通函數的優點呢,那就是內聯函數,內聯函數 的生成,只需要在普通函數的前面加 inline關鍵字即可。
優點: | 避免調用時的額外開銷(入棧出棧) |
代價: | 由於內聯函數的函數體在代碼段中會出現多個“副本”,因此會增加代碼段的空間 |
本質: | 以犧牲代碼段空間爲代價,提高程序的運行時效率 |
適用場景: | 函數體很 小,且被 頻繁 調用 |
inline int sqr(int x) { return x*x; }
匿名函數:
學過python的同學應該知道匿名函數,看上去沒有函數名,只有一個函數體。簡短的函數,就地書寫。lambda是匿名函數的英文翻譯,也是一個表達式。先看看lambda表達式的結構:[capture](paras)mutable->returntype{statement}。
[capture]:捕獲列表。總是出現在 lambda 函數的開始處。事實上 [] 是 lambda 的引用符。換句話說,編譯器根據引出符判斷接下來的代碼是否是lamba函數。
(paramers):參數列表。與普能函數的參數列表一致。如果不需要傳遞參數,可以 連同()一起省略。
mutable:默認情況下,lambda函數總是一個 const 函數,mutable 可以取消其常 量性。在使用該修飾符時,參數列表不可以省略(即使參數爲空)。
->return-type:返回類型。用於追蹤返回類型形式聲明函數的返回類型。出於方便, 不需要返回值的時候可以連同->一起省略。此外返回類型明確的情況下,也可以省略該部分。
{statement}:函數體。內容與普通函數一樣,不過除了可以使用參數之外,還可以使用所有捕獲的變量。
//lambda
//lambda函數總是一個const函數
//mutable: 可以取消其常量性
#include <iostream>
using namespace std;
int main()
{
/*[]{} 閉包+函數體 */
auto foo = []{ return 1 +2;};
cout<<foo()<<endl; //3
cout<<[]{ return 1+2;}()<<endl; //3
/* [](){} 閉包+參數+函數體 */
auto foo1 = [](int x, int y){ return x+y;};
cout<<foo1(1,2)<<endl; //3
cout<<[](int x, int y){ return x+y;}(1, 3)<<endl; //4
/* []()->{}閉包+參數+返回值+函數體 */
auto foo2 = [](int x, int y)->int{ return x+y ;};
cout<<foo2(1, 2)<<endl; //3
cout<<[](int x, int y)->int{ return x+y;}(2, 4)<<endl; //6
int x = 10; int y = 100;
cout<<"main:"<<x<<y<<endl; //10100
/* []()mutable->{}閉包+參數+可修改+返回值+函數體 */
auto foo3 = [=]()mutable{
x = 20;
y = 200;
cout<<"lambda:"<<x<<y<<endl; //20200
};
foo3();
cout<<"main:"<<x<<y<<endl; //10100
return 0;
}
多嘴一句 []閉包:
lambda函數能夠捕獲 lambda函數外的具有自動存儲時期的變量。函數體與這些變 量的集合合起來叫閉包。閉包的概念在lambda中通過[]來體現出來。
- [] 不截取任何變量。
- [bar] 僅對外部變量 bar值傳遞在函數體中使用。
- [&bar] 僅對外部變量 bar引用傳遞在函數體中使用。
- [x,&y] 僅x按值傳遞,y按引用傳遞在函數體中使用。。
- [&} 截取外部作用域中所有變量,並作爲引用傳遞在函數體中使用。
- [=] 截取外部作用域中所有變量,並按值傳遞在函數體中使用。
- [=,&foo] 截取外部作用域中所有變量,並值傳遞在函數體中使用,但是對 foo 變量使用引用傳遞。
- [&,=foo] 截取外部作用域中所有變量,在函數體中作引用傳遞使用,但是對 foo變量作值傳遞。
上述中涉及到 值傳遞 要發生 拷貝 行爲,而引用傳遞則不會發生拷貝行爲。捕獲列表中不允許重複。比如:[=,a] [&,&this]。 閉包的本質,初始化lamda表達式。
仿函數
cpp官方及其它作者已有詳細解釋請跳轉閱讀:
https://blog.csdn.net/K346K346/article/details/82818801
定義:仿函數(Functor)又稱爲函數對象(Function Object)是一個能行使函數功能的類。仿函數的語法幾乎和我們普通的函數調用一樣,不過作爲仿函數的類,都必須重載operator()運算符。因爲調用仿函數,實際上就是通過類對象調用重載後的operator()運算符。
Demo1:
//仿函數例子1
#include <iostream>
using namespace std;
class Add{
public:
int operator()(int x, int y){
return x + y;
}
};
int main()
{
int a = 1, b = 2;
Add add;
cout<<add(a, b)<<endl; //3
return 0;
}
Demo2:
//帶狀態的 functor
//相對於函數,仿函數可以擁有初始狀態,一般通過class定義私有成員,並在聲明對象的時候,
//進行初始化。私有成員的狀態,就成了仿函數的初始狀態。而由於聲明一個仿函數對象可以擁有多個不同初始狀態的實例
#include <iostream>
using namespace std;
class Tax{
public:
Tax(float r, float b):_rate(r), _base(b){}
float operator()(float money){
return (money - _base)*_rate;
}
private:
float _rate;
float _base;
};
int main()
{
Tax high(0.40, 30000);
Tax mid(0.25, 20000);
Tax low(0.12, 10000);
cout<<"大於3W的稅:"<<high(37500)<<endl;
cout<<"大於2W的稅:"<<mid(27500)<<endl;
return 0;
}
仿函數還和lambda合作使用:
#include <iostream>
using namespace std;
class Tax{
public:
Tax(float r, float b):_rate(r), _base(b){}
float operator()(float money){
return (money - _base)*_rate;
}
private:
float _rate;
float _base;
};
int main()
{
float rate = 0.40;
float base = 30000;
auto high = [&](float money){
return (money - base)*rate;
};
//Tax high(0.40, 30000);
//Tax mid(0.25, 20000);
//Tax low(0.12, 10000);
cout<<"大於3W的稅:"<<high(37500)<<endl;
//cout<<"大於2W的稅:"<<mid(27500)<<endl;
return 0;
}