c++入門
1.基礎的C++程序編寫
1.1:第一個程序
#include<iostream>
using namespace std;
int main(){
cout<<"hello world!"<<endl;
return 0;
}
1.2:C++與C語言第一點區別(輸入,輸出和頭文件)
關於C語言中
#include<stdio,h>
和c++中#include<iostream>
的異同請查看https://blog.csdn.net/weixin_36250487/article/details/80293539
1.cin: 表示標準輸入(standard input)的istream類對象。通過 cin 往程序中寫入數據
2.cout : 表示標準輸出(standard output)的ostream類對象。通過cout 從設備輸出或者寫數據。
2: 命名空間
細心的你會發現第一個c++程序中有一句
using namespace std;
了吧。
其原因爲:所謂namespace,是指標識符的各種可見範圍。C++標準程序庫中的所有標識符都被定義於一個名爲std的namespace中。
2.1引入namepase 的目的
解決命名衝突的問題
在C語言中不能出現兩個名字相同的變量或者函數,因此C++引入命名空間的目的就是解決命名空間衝突的問題
#include<iostream>
using namepase std;
namespase A{
int a=10;
}
namespace B{
int a=20;
}
int main(){
cout<<A::a<<endl;
cout<<B::a<<endl;
return 0;
}
該程序輸出的兩個a值是不同的,就像這樣引入命名空間後你想要使用那個a值只需要用作用域限定符 :: 告訴編輯器即可。
2.2:命名空間的使用
1.加命名空間名稱及作用域限定符
int main()
{
printf("%d\n", A::a);
return 0;
}
2.使用using 將命名空間中成員引入
using A::a;
int main()
{
printf("%d\n", A::a);
return 0;
}
3.使用using namespace 命名空間名稱引入
using namespce N;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
Add(10, 20);
return 0;
}
4.其他用法
namespace N1{
int a = 10;
int b = 20;
int Add(int a, int b){
//命名空間中可以有函數的存在
return a + b;
}
namespace N2{
//命名空間中可以出現嵌套式的命名空間
//N1::N2::swap(x,y);
int Swap(int x, int y){
return x*y;
}
}
}
命名空間總結見:
https://blog.csdn.net/jack_wang128801/article/details/90272870
3.缺省參數
缺省參數是聲明或定義函數時爲函數的參數指定一個默認值。在調用該函數時,如果沒有指定實參則採用該默 認值,否則使用指定的實參。
void TestFunc(int a = 0)
{
cout<<a<<endl;
}
int main()
{
TestFunc(); // 沒有傳參時,使用參數的默認值
TestFunc(10); // 傳參時,使用指定的實參
}
3.1全缺省參數
void Date(int year=2019,int month=6,int day=3){
cout<<"year= "<<year<<endl;
cout<<"month= "<<month<<endl;
cout<<"day= "<<day<<endl;
}
3.2半缺省參數
void Date(int year,int month=3,int day=6){}
//【注意】半缺省參數必須從右往左來給出,不可以間隔給出
// 缺省參數不能在函數聲明和定義中同時出現
//缺省值必須是常量或者全局變量
【總結】:所謂的缺省參數是指形參是否被賦予初始值
4.函數重載
4.1 概念:c++允許在同一作用域中聲明幾個功能相似的幾個同名函數,這些函數的形參列表(參數的個數或類型或順序)必須不同。
常用來處理實現功能類似數據類型不同的問題
//函數重載實例
#include<iostream>
#include<windows.h>
using namespace std;
int Add(int a,int b){
return a + b;
}
double Add(double a, double b){
//相較第一個而言形參的類型不同
return a + b;
}
double Add(double a, int b, float c){
//相較第一個而言形參的類型不同並且有參數個數的不同
return a + b + c;
}
int main(){
int a = 10;
int b = 20;
double c = 2.0;
double d = 3.0;
float f = 3;
cout<<Add(a,b)<<endl;
cout << Add(c, d) << endl;
cout << Add(d, a, f) << endl;
system("pause");
return 0;
}
//c語言中函數名相同會提示重定義錯誤
[小結]:構成函數重載時參數需滿足的條件
1.函數參數個數不同
2.參數類型不同
3.參數順序不同
【注】:函數名相同,參數相同.但是返回值類型不同的函數不能構成函數重載
4.2名字修飾
在c/c++中,一個程序要運行需要經歷:預處理、編輯、彙編、鏈接等步驟
5.2.1 預處理:去註釋,宏替換,頭文件展開
5.2.2 彙編:語法檢查,轉換成彙編代碼
5.2.3 編譯:彙編代碼————>機器碼
5.2.4 鏈接:生成可執行文件
5.2.5 在Linux下的修飾
void F1(int a);
——Z2F1I
其中—Z爲前綴,2代表函數名有兩個字符,F1爲函數名,而i(int)爲參數的類型的首字母
void F1(char b);
//_Z2F1C
void F1(int a,char b);
//——Z2F1IC
【小結】:在Linux系統下函數的編譯按一下幾個步驟分塊
1.前綴–z
2.函數名的字符個數
3.函數的名字
4.函數參數列表中參數類型的首字母(所有參數的首字母)
5.2.6 C語言的名字修飾規則非常簡單,只是在函數名字前面加一個下劃線(_函數名),
- 而在C++文件(xx.cpp)中在函數名字前面加一個 extern "c"既是告訴編輯器該函數按照c語言規則來編譯
extern "c" int Add(int a,int b)
//一個函數的形式
// 若需要全部都用C語言的風格來編輯,形式如下
extern "c"{
//很多行C語言代碼;
}
- 函數重載總結
- 1 C語言不支持,C++支持
- 2 特點:函數名相同,參數不同(1.類型不同。2.順序不同 3.參數個數不同)
- 3 C++語言支持重載:函數名修飾規則 name mangling
- 4 C語言中底層函數名:_ 函數名
- 5 C++底層函數名: 前綴+函數名+參數類型首字母
6:引用
6.1 引用概念:引用不是重新定義了一個變量,而是給已經存在的變量取個別名,編譯器不會爲引用變量開闢內存空間。它和它引用的變量公用同一塊內存空間
- 示例代碼如下:
- 引用格式:類型名& 引用變量名(對象名) = 引用對象
#include<iostream>
#include<windows.h>
using namespace std;
void Testfun(){
int a = 10;
int &ra = a;
//int b=20;
cout << ra << endl;
//結果出現一個10
cout << ra <<endl<< a<<endl;
//結果出現兩個10
}
int main(){
Testfun();
system("pause");
return 0;
}
//ra=b 不是引用,表達式含義是將變量 b 的值放到 a
//&ra = b 不是引用,單獨的&ra表示ra取地址
- 監視結果:&ra與&a的值相同,ra與a值都爲10;
【注意】:定義的引用類型必須與被引用對象的類型相同
如上例中如果出現 double &ra=a; 則會報錯
6.2 引用特性
- 1.引用在定義時必須初始化
- 錯誤示例: int &ra;
- 2.一個變量可以有多個引用
//正確用法示例:
int a=10;
int &ra=a;
int &rra=a;
int &rrra=a;
- 3.引用一旦引用一個實體則不能再引用其他實體
6.3 常引用
void TestContRef(){
const int a=10;//常引用
//int & ra=1; //該語句編譯時出錯,a爲常量
const int &ra=a; //常引用
//int& b=10; //該語句編譯時出錯,b爲常量
const int& b=10; //常引用
double d=12.34;
const int& rd=d;//常引用(rd指向的是隱式類型轉換時生成的臨時變量)
int c=d; //隱式類型轉換--->臨時變量具有常性
//int& rd=d; //該語句編譯時出錯,類型不同
}
6.4 使用場景
- 1. 做參數
- 傳指針和傳引用效果一樣;
示例代碼:
#include<iostream>
#include<windows.h>
using namespace std;
void Swap(int *pa,int *pb){
int tmp = *pa;
*pa = *pb;
*pb = tmp;
}
void Swap(int &ra,int &rb){
int tmp = ra;
ra = rb;
rb = tmp;
}
//上面的兩個函數修改的是你上傳的參數它所指向的空間的值
#include<iostream>
#include<stdlib.h>
using namespace std;
void swap(int *pa, int *pb){
int temp=*pa; //pa的地址與主函數中x地址相同
*pa = *pb; //pb的地址與主函數中x地址相同
*pb = temp;
//交換的是兩個參數原來地址空間中的值,
//既原函數中定義的變量x,y的地址空間中的值被交換了
}
//void swap(int &a, int &b){
// int temp = a;
// a = b;
// b = temp;
// //交換的是兩個參數原來地址空間中的值,
// //既原函數中定義的變量x,y的地址空間中的值被交換了
//}
void swap(int a, int b){
int temp = a;
a = b;
b = temp;
//交換的是臨時拷貝創建的空間裏的值
//其原地址空間內的值並沒有被交換
}
int main(){
int x = 10;
int y = 20;
swap(&x, &y);
swap(x, y);
system("pause");
return 0;
}
【小結】:傳指針與傳引用的效果是一樣的(都是與原來的變量的地址空間打交道),而傳值時會發生臨時拷貝(代價太大),所以相比較而言傳指針和傳引用的效率比較高
- 2.做返回值
#include<iostream>
#include<windows.h>
using namespace std;
int& Testfuc(int &a){
a+=10;
return 0;
}
int main(){
int a = 10;
int &ra=Testfuc(a);
system("pause");
return 0;
}
//此程序中的&ra和&a都指向變量a的地址
【注意】:如果函數返回時,離開函數作用域後,其棧上空間已經還給了系統,因此不能用函數棧幀上的空間做爲引用類型的返回值,如果函數類型返回,返回值的生命週期必須不受函數的限制(既函數週期比較長)
#include<iostream>
#include<windows.h>
#include<time.h>
using namespace std;
int main(){
size_t beginl = clock();
for (int i = 0; i < 10000; i++){
cout << "i=" << i << endl;
}
size_t endl = clock();
cout << (endl- beginl) /CLOCKS_PER_SEC<< endl;
system("pause");
return 0;
}
//以毫秒的形式輸出循環的執行時間
//引用傳值的效率高於值拷貝的形式
6.6 指針與引用的區別
- 引用語法層面:和指針指向同一塊內存空間,引用本身沒有開闢新的空間
- 引用底層實現:引用開闢新的空間,和指針的的實現相同
int a=10; int a=10;
mov dword ptr [a], OAh mov dword ptr [a], 0Ah
int& ra = a; int* pa = &a;
lea eax, [a] lea еах,[a]
mov dword ptr [ra], eax mov dword ptr [pa], eax
//指針的解引用和引用的解引用底層實現時一樣的
ra = 20; *pa = 20;
mov eax, dword ptr [ra] mov eax, dword ptr[ра]
mov dword ptr [eax], 14h mov dword ptr [eax], 14h
引用和指針的不同點:
6.6.1. 引用在定義時必須初始化,指針沒有要求
6.6.2. 引用在初始化時引用一個實體後,就不能再引用其他實體,而指針可以在任何時候指向任何一個同類型實體
6.6.3. 沒有NULL引用,但有NULL指針
*6.6.4. 在sizeof中含義不同:引用結果爲引用類型的大小,但指針始終是地址空間所佔字節個數(32位平臺下佔4個字節)cout << sizeof(a)<<endl; //結果爲4
6.6.5. 引用自加即引用的實體增加1,指針自加即指針向後偏移一個類型的大小
6.6.6. 有多級指針,但是沒有多級引用
6.6.7. 訪問實體方式不同,指針需要顯式解引用,引用編譯器自己處理
**6.6.8. 引用比指針使用起來相對更安全 ** (引用使用時必須初始化)
7.內聯函數
7.1 概念:以inline修飾的函數叫做內聯函數,編譯時C++編譯器會在調用內聯函數的地方展開,沒有函數壓棧的開銷,內聯函數提升程序運行的效率。
inline Add(int a,int b){
return a+b;
}
int main(){
int ret=Add(1,3) ------->//若是內斂函數的話,函數體直接展開成int ret=1+3;
//如果不是內聯函數的話,底層實現會調用add的函數棧幀call add(0X001)
return 0;
}
對於普通函數和內聯函數的比較
#include<iostream>
using namespace std;
int add(int a,int b){
return a+b;
}
inline int add(int a,int b){
return a+b;
}
int main(){
int ret=add(1,3)
return 0;
}
普通函數
int add(int a,int b){
return a+b;
}底層實現:普通函數,編譯時調用add的函數棧幀
push 3
push 1
call add(0821042h)
內聯函數
inline int add(int a,int b){
return a+b;
}底層實現:內聯函數,編譯時函數體展開
mov eax ,2
00012220 add
00012223 mov dword ptr[ret] ,eax
7.2 inline函數的特性
- inline是一種以空間換時間的做法,省去調用函數額開銷。所以代碼很長或者有循環/遞歸的函數不適宜使用內聯函數。
- inline對於編譯器而言只是一個建議,編譯器會自動優化,如果定義爲inline的函數體內有循環/遞歸等,編譯器優化時會忽略掉內聯。
- inline不建議聲明和定義分離,分離會導致鏈接錯誤。因爲inline被展開,就沒有函數地址了,鏈接就會找不到
面試題:
- 【面試題】宏的優缺點?
優點:
- 1.增強代碼的複用性。
- 2.提高性能。
缺點:
- 1.不方便調試宏。(因爲預編譯階段進行了替換)
- 2.導致代碼可讀性差,可維護性差,容易誤用。
- 3.沒有類型安全的檢查 。
C++有哪些技術替代宏
- 1. 常量定義 換用const
- 2. 函數定義 換用內聯函數
8.關鍵字auto
- 特點:使用auto定義變量時必須對其進行初始化,在編譯階段編譯器需要根據初始化
- 表達式來推導auto的實際類型。因此auto並非是一種“類型”的聲明,而是一個類型聲明時的“佔位符”,編譯器在編譯期會將auto替換爲變量實際的類型。
#include<iostream>
#include<windows.h>
using namespace std;
int main(){
int a = 10;
auto b = a;
double c = 20.3;
auto d = c;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
system("pause");
return 0;
}
//顯示出了類型的名稱
8.2.1. auto與指針和引用結合起來使用用auto聲明指針類型時,用auto和auto*沒有任何區別,但用auto聲明引用類型時則必須加&
int main()
{
int x = 10;
auto a = &x; //定義了一個引用
auto* b = &x; //(int *)
auto& c = x; //推導出引用變量的類型
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
*a = 20;
*b = 30;
c = 40;
return 0;
}
8.2.2 在同一行定義多個變量
- 當在同一行聲明多個變量時,這些變量必須是相同的類型,否則編譯器將會報錯,
- 爲編譯器實際只對第一個類型進行推導,然後用推導出來的類型定義其他變量。
void TestAuto()
{
auto a = 1, b = 2;
auto c = 3, d = 4.0; // 該行代碼會編譯失敗,因爲c和d的初始化表達式類型不同
}
- 若同一個程序中同時定義了多個 auto ,其會根據第一個變量表達式去推導類型。
8.3 auto不能推導的場景。
8.3.1 auto不能做函數的參數
// 此處代碼編譯失敗,auto不能作爲形參類型,因爲編譯器無法對a的實際類型進行推導
void TestAuto(auto a)
{}
8.3.2 auto不能直接用來聲明數組
void TestAuto()
{
int a[] = {1,2,3};
auto b[] = {4,5,6};
}
8.3.3爲了避免與C++98中的auto發生混淆,C++11只保留了auto作爲類型指示符的用法
8.3.4 auto在實際中最常見的優勢用法就是跟以後會講到的C++11提供的新式for循環,還有lambda表達式等進
行配合使用。
8.3.5 auto不能定義類的非靜態成員變量(暫不做講解,後面講)
8.3.6實例化模板時不能使用auto作爲模板參數(暫不做講解,後面講)
9. 基於範圍的for循環(C++11)
9.1 for循環後的括號由冒號“ :”分爲兩部分:第一部分是範圍內用於迭代的變量,第二部分則表示被迭代的範圍。
#include<iostream>
#include<windows.h>
using namespace std;
int main(){
int arr[] = {1,2,3,4,5,6,7,8,9};
int size = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (; i < size; i++){
cout << "arr1[i]=" << arr[i] << endl;
}
cout << endl;
for (int e : arr){
cout << "arr2[i]=" << e << endl;
}
cout << endl;
for (auto& e : arr){
cout << "arr3[i]=" <<e << endl;
}
system("pause");
return 0;
}
【注意】:與普通循環類似,基於範圍的for循環可以用continue來結束本次循環,也可以用break來跳出整個循環
9.2 範圍for的使用條件
1. for循環迭代的範圍必須是確定的
- 對於數組而言,就是數組中第一個元素和最後一個元素的範圍;對於類而言,應該提
- 供begin和end的方法,begin和end就是for循環迭代的範圍。
- 注意:以下代碼就有問題,因爲for的範圍不確定
void TestFor(int array[])
{
for(auto& e : array)
cout<< e <<endl;
}
10.指針空值nullptr(C++11)
#include<iostream>
#include<windows.h>
using namespace std;
int main(){
int *p = NULL;//c++中此處的NULL被定義成常數 "0"
int *pp = nullptr;
cout << typeid(pp).name() << endl;
cout << typeid(nullptr).name() << endl;
system("pause");
return 0;
}
結果如下:
//int *
//std::nullptr_t
程序本意是想通過f(NULL)調用指針版本的f(int)函數,但是由於NULL被定義成 0,因此與程序的初衷相悖。在C++98中,字面常量0既可以是一個整形數字,也可以是無類型的指針(void)常量,但是編譯器默認情況下將其看成是一個整形常量,如果要將其按照指針方式來使用,必須對其進行強轉(void*)0。**
10.2 nullptr 與 nullptr_t
nullptr代表一個指針空值常量。nullptr是有類型的,其類型爲nullptr_t,僅僅可以被隱式轉化爲指針類型,nullptr_t被定義在頭文件中
typedef decltype(nullptr) nullptr_t;
【注意:】
1 在使用nullptr表示指針空值時,不需要包含頭文件,因爲nullptr是C++11作爲新關鍵字引入的。
2 在C++11中,sizeof(nullptr) 與 sizeof((void*)0)所佔的字節數相同。
3 爲了提高代碼的健壯性,在後續表示指針空值時建議最好使用nullptr。