阿里云備案做網(wǎng)站seo怎么賺錢(qián)
目錄
1、C++發(fā)展歷史
2、C++版本更新
3、C++參考文檔
4、C++書(shū)籍推薦
5、C++的程序
6、命名空間
6.1 namespace的作用
6.2 namespace的定義
6.3 namespace的使用
7、C++輸入&輸出
8、缺省參數(shù)
9、函數(shù)重載
10、引用
10.1?引用的概念和定義
10.2 引用的特性
10.3 引用的使用
10.4 const引用
10.5 指針和引用的關(guān)系(面試高頻考查點(diǎn))
11、inline
12、nullptr
1、C++發(fā)展歷史
C++的起源可以追溯到1979年,當(dāng)時(shí)Bjarne Stroustrup(本賈尼·斯特勞斯特盧普,這個(gè)翻譯的名字不同的地方可能有差異)在貝爾實(shí)驗(yàn)室從事計(jì)算機(jī)科學(xué)和軟件工程的研究工作。面對(duì)項(xiàng)目中復(fù)雜的軟件開(kāi)發(fā)任務(wù),特別是模擬和操作系統(tǒng)的開(kāi)發(fā)工作,他感受到了現(xiàn)有語(yǔ)言(如C語(yǔ)言)在表達(dá)能力、可維護(hù)性和可擴(kuò)展性方面的不足。
1983年,Bjarne Stroustrup在C語(yǔ)言的基礎(chǔ)上添加了面向?qū)ο缶幊痰奶匦?#xff0c;設(shè)計(jì)出了C++語(yǔ)言的雛形, 此時(shí)的C++已經(jīng)有了類(lèi)、封裝、繼承等核心概念,為后來(lái)的面向?qū)ο缶幊痰於嘶A(chǔ)。這一年該語(yǔ)言被正式命名為C++。
在隨后的幾年中,C++在學(xué)術(shù)界和工業(yè)界的應(yīng)用逐漸增多。一些大學(xué)和研究所開(kāi)始將C++作為教學(xué)和研究的選語(yǔ)言,而一些公司也開(kāi)始在產(chǎn)品開(kāi)發(fā)中嘗試使用C++。這一時(shí)期,C++的標(biāo)準(zhǔn)庫(kù)和模板等特性也得到了進(jìn)一步的完善和發(fā)展。
C++的標(biāo)準(zhǔn)化(使得 C++ 代碼在不同的編譯器下表現(xiàn)一致)工作于1989年開(kāi)始,并成立了一個(gè)ANSI和ISO(International Standards Organization)國(guó)際標(biāo)準(zhǔn)化組織的聯(lián)合標(biāo)準(zhǔn)化委員會(huì)。1994年標(biāo)準(zhǔn)化委員會(huì)提出了第一個(gè)標(biāo)準(zhǔn)化草案。在該草案中,委員會(huì)在保持斯特勞斯特盧普最初定義的所有特征的同時(shí),還增加了部分新特征。
在完成C++標(biāo)準(zhǔn)化的第一個(gè)草案后不久,STL(Standard Template Library)是惠普實(shí)驗(yàn)室開(kāi)發(fā)的系列軟件的統(tǒng)稱(chēng)。它是由Alexander Stepanov、Meng Lee和David R Musser在惠普實(shí)驗(yàn)室工作時(shí)所開(kāi)發(fā)出來(lái)的。在通過(guò)了標(biāo)準(zhǔn)化第一個(gè)草案之后,聯(lián)合標(biāo)準(zhǔn)化委員會(huì)投票并通過(guò)了將STL包含到C++標(biāo)準(zhǔn)中的提議。STL對(duì)C++的擴(kuò)展超出C++的最初定義范圍。雖然在標(biāo)準(zhǔn)中增加STL是個(gè)很重要的決定,但也因此延緩了C++標(biāo)準(zhǔn)化的進(jìn)程。
1997年11月14日,聯(lián)合標(biāo)準(zhǔn)化委員會(huì)通過(guò)了該標(biāo)準(zhǔn)的最終草案。1998年,C++的ANSI/IS0標(biāo)準(zhǔn)被投入使用。
2、C++版本更新
C++98:這是第一個(gè) ANSI/ISO 標(biāo)準(zhǔn)化的 C++ 版本,發(fā)布于 1998 年。它基于 Bjarne Stroustrup 于 1985 年創(chuàng)建的原始 C++ 設(shè)計(jì),并加入了一些重要特性,如 STL(標(biāo)準(zhǔn)模板庫(kù))、異常處理、I/O Streams、命名空間和 RTTI(運(yùn)行時(shí)類(lèi)型識(shí)別)。
C++03:這個(gè)版本主要是對(duì) C++98 的一些修正和改進(jìn),發(fā)布于 2003 年,并未引入新的語(yǔ)言特性,所以一般不把它當(dāng)做重要版本,存在感也不強(qiáng)。
C++11:這是 C++ 歷史上最重大的更新之一,有時(shí)被稱(chēng)為 C++0x(因?yàn)樗?jì)劃在 200x 年發(fā)布,一直跳票)。它引入了大量新特性,如自動(dòng)類(lèi)型推斷(auto 關(guān)鍵字)、基于范圍的 for 循環(huán)、Lambda 表達(dá)式、智能指針、并發(fā)支持、移動(dòng)語(yǔ)義、nullptr 和更強(qiáng)大的模板功能等。
C++14:作為 C++11 的小幅度更新,C++14 引入了一些改進(jìn)和新特性,包括泛型 Lambda 表達(dá)式、返回類(lèi)型推導(dǎo)、二進(jìn)制字面量、數(shù)字分隔符、棄用屬性等。
C++17:這個(gè)版本進(jìn)一步提升了 C++ 的功能和易用性,新功能不是很多,引入了結(jié)構(gòu)化綁定、if constexpr、std::optional、std::variant、std::string_view、并行算法等特性。
C++20 是繼 C++11 之后又一個(gè)重大更新,引入了概念(concepts)、范圍庫(kù)(ranges)、協(xié)程(coroutines)、模塊(modules)、三元運(yùn)算符的改進(jìn)、constexpr 的增強(qiáng)、std::span 等新特性。
C++23 是 2023 年 7 月份剛確定下的新標(biāo)準(zhǔn),目前能完整支持 C++23 的編譯器基本沒(méi)有。變化包括引入標(biāo)準(zhǔn)庫(kù)的模塊化支持、擴(kuò)展 constexpr 、增加并行算法、ranges 擴(kuò)展、this 推導(dǎo)、引入更多的屬性和注解、增加 std::mdspan、std::generator 等新特性。
3、C++參考文檔
不是C++官官方文檔,標(biāo)準(zhǔn)也只更新到C++11,但是以頭文件形式呈現(xiàn),內(nèi)容比較易看好懂
Reference - C++ Reference
是C++官方文檔的英文版,更新到了最新的C++標(biāo)準(zhǔn),但相比第一個(gè)不那么易看
https://en.cppreference.com/w/
是C++官方文檔的中文版
https://zh.cppreference.com/w/cpp
4、C++書(shū)籍推薦
C++ Primer:主要講解語(yǔ)法,經(jīng)典的語(yǔ)法書(shū)籍,可以作為語(yǔ)法字典,非常好用。
STL源碼剖析:主要從底層實(shí)現(xiàn)的角度結(jié)合STL源碼,庖丁解牛式剖析STL的實(shí)現(xiàn),是侯捷老師的經(jīng)典之作??梢院芎玫膸椭覀儗W(xué)習(xí)別人用語(yǔ)法是如何實(shí)現(xiàn)出高效簡(jiǎn)潔的數(shù)據(jù)結(jié)構(gòu)和算法代碼,如何使用泛型封裝等。讓我們不再坐井觀天,閉門(mén)造車(chē),適合中后期可以看。
Effctive C++:本書(shū)也是侯捷老師翻譯的,本書(shū)有的一句評(píng)價(jià),把C++程序員分為看過(guò)此書(shū)的和沒(méi)看過(guò)此書(shū)的。本書(shū)主要講了55個(gè)如何正高效使用C++的條款,建議中后期可以看一遍,工作1-2年后再看遍,相信會(huì)有不一樣的收獲。
5、C++的程序
C++兼容C語(yǔ)言絕大多數(shù)的語(yǔ)法,所以C語(yǔ)言實(shí)現(xiàn)的hello world依舊可以運(yùn)行,C++中需要把定義文件代碼后綴名改為.cpp,vs編譯器看到是.cpp就會(huì)調(diào)用C++編譯器編譯,linux下要用g++編譯,不再是gcc
// 兼容C語(yǔ)言
// test.cpp
#include<stdio.h>
int main()
{printf("hello world\n");return 0;
}
當(dāng)然C++有一套自己的輸入輸出,嚴(yán)格說(shuō)C++版本的hello world應(yīng)該是這樣寫(xiě)的
// test.cpp
// 這里的std cout看不懂,沒(méi)關(guān)系,下面會(huì)依次講解
#include<iostream>
using namespace std;int main()
{cout << "hello world\n" << endl;return 0;
}
6、命名空間
6.1 namespace的作用
在C/C++中,變量、函數(shù)和后面要學(xué)到的類(lèi)都是大量存在的,這些變量、函數(shù)和類(lèi)的名稱(chēng)將都存在于全局作用域中,可能會(huì)導(dǎo)致很多沖突。
使用命名空間的目的是對(duì)標(biāo)識(shí)符的名稱(chēng)進(jìn)行本地化,以避免命名沖突或名字污染,namespace關(guān)鍵字的出現(xiàn)就是針對(duì)這種問(wèn)題的。
c語(yǔ)言項(xiàng)目類(lèi)似下面程序這樣的命名沖突是普遍存在的問(wèn)題,如:
#include <stdio.h>
#include <stdlib.h>
int rand = 10;
int main()
{// 編譯報(bào)錯(cuò):error C2365: “rand”: 重定義;以前的定義是“函數(shù)”printf("%d\n", rand);return 0;
}
在#include <stdlib.h>中,有rand函數(shù)(int rand (void);),此時(shí)不知道是rand是變量名,函數(shù)名
6.2 namespace的定義
? 定義命名空間,需要使用到namespace關(guān)鍵字,后面跟命名空間的名字,然后接一對(duì){}即可,{}中即為命名空間的成員。命名空間中可以定義 變量 / 函數(shù) / 類(lèi)型 等。
? namespace本質(zhì)是定義出一個(gè)域,這個(gè)域跟全局域各自獨(dú)立,不同的域可以定義同名變量,所以下面的rand不在沖突了。
? C++中域有 函數(shù)局部域,全局域,命名空間域,類(lèi)域。域影響的是編譯時(shí)語(yǔ)法查找一個(gè) 變量 / 函數(shù) / 類(lèi)型 出處(聲明或定義)的邏輯,所以有了域隔離,名字沖突就解決了。局部域和全局域除了會(huì)影響編譯查找邏輯,還會(huì)影響變量的生命周期,命名空間域和類(lèi)域不影響變量生命周期。
? namespace只能定義在全局,當(dāng)然他還可以嵌套定義。
? 項(xiàng)目工程中多文件中定義的同名namespace會(huì)認(rèn)為是一個(gè)namespace,不會(huì)沖突。
? C++標(biāo)準(zhǔn)庫(kù)都放在一個(gè)叫std(standard)的命名空間中。
#include <stdio.h>
#include <stdlib.h>// 1. 正常的命名空間定義
// Lzc是命名空間的名字,?般開(kāi)發(fā)中是用項(xiàng)目名字做命名空間名
// 日常練習(xí)可以用自己的名字命名
namespace Lzc
{// 命名空間中可以定義變量/函數(shù)/類(lèi)型int rand = 10;int Add(int left, int right){return left + right;}struct Node{struct Node* next;int val;};
}int main()
{printf("%p\n", rand);// 打印rand函數(shù)的地址printf("%d\n", Lzc::rand);// 打印Lzc命名空間里的rand變量return 0;
}//2. 命名空間可以嵌套
namespace S
{namespace Lzc{int rand = 1;int Add(int left, int right){return left + right;}}namespace Ysy{int rand = 2;int Add(int left, int right){return (left + right) * 10;}}
}int main()
{printf("%d\n", S::Lzc::rand);// 1printf("%d\n", S::Ysy::rand);// 2printf("%d\n", S::Lzc::Add(1, 2));// 3printf("%d\n", S::Ysy::Add(1, 2));// 30return 0;
}//3. 多文件中可以定義同名namespace,他們會(huì)默認(rèn)合并到?起,就像同一個(gè)namespace一樣
// Stack.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
namespace Lzc
{typedef int STDataType;typedef struct Stack{STDataType* a;int top;int capacity;}ST;void STInit(ST* ps, int n);void STDestroy(ST* ps);void STPush(ST* ps, STDataType x);void STPop(ST* ps);STDataType STTop(ST* ps);int STSize(ST* ps);bool STEmpty(ST* ps);
}// Stack.cpp
#include"Stack.h"
namespace Lzc
{void STInit(ST* ps, int n){assert(ps);ps->a = (STDataType*)malloc(n * sizeof(STDataType));ps->top = 0;ps->capacity = n;}// 棧頂void STPush(ST* ps, STDataType x){assert(ps);// 滿(mǎn)了, 擴(kuò)容if (ps->top == ps->capacity){printf("擴(kuò)容\n");int newcapacity = ps->capacity == 0 ? 4 : ps->capacity* 2;STDataType* tmp = (STDataType*)realloc(ps->a,newcapacity * sizeof(STDataType));if (tmp == NULL){perror("realloc fail");return;}ps->a = tmp;ps->capacity = newcapacity;}ps->a[ps->top] = x;ps->top++;}//...
}// test.cpp
#include"Stack.h"
// 全局定義了一份單獨(dú)的Stack
typedef struct Stack
{int a[10];int top;
}ST;
void STInit(ST* ps) {}
void STPush(ST* ps, int x) {}int main()
{// 調(diào)用全局的ST st1;STInit(&st1);STPush(&st1, 1);STPush(&st1, 2);printf("%d\n", sizeof(st1));// 調(diào)用S namespace的Lzc::ST st2;printf("%d\n", sizeof(st2));Lzc::STInit(&st2, 4);Lzc::STPush(&st2, 1);Lzc::STPush(&st2, 2);return 0;
}
6.3 namespace的使用
編譯查找一個(gè)變量的聲明/定義時(shí),默認(rèn)只會(huì)在局部或者全局查找,不會(huì)到命名空間里面去查找。所以下面程序會(huì)編譯報(bào)錯(cuò)。我們要使用命名空間中定義的變量/函數(shù),有三種方式:
? 指定命名空間訪(fǎng)問(wèn),項(xiàng)目中推薦這種方式。
? using將命名空間中某個(gè)成員展開(kāi),項(xiàng)目中經(jīng)常訪(fǎng)問(wèn)的不存在沖突的成員推薦這種方式。
? using展開(kāi)命名空間中全部成員,項(xiàng)目不推薦,沖突風(fēng)險(xiǎn)很大,日常小練習(xí)程序?yàn)榱朔奖阃扑]使用。
#include<stdio.h>namespace Lzc
{int a = 0;int b = 1;
}int main()
{// 編譯報(bào)錯(cuò):error C2065: “a”: 未聲明的標(biāo)識(shí)符printf("%d\n", a);return 0;
}//1. 指定命名空間訪(fǎng)問(wèn)
int main()
{printf("%d\n", Lzc::a);return 0;
}//2. using將命名空間中某個(gè)成員展開(kāi)
using Lzc::b;
int main()
{printf("%d\n", Lzc::a);printf("%d\n", b);printf("%d\n", b);printf("%d\n", b);printf("%d\n", b);printf("%d\n", b);return 0;
}//3. 展開(kāi)命名空間中全部成員
using namespace Lzc;
int main()
{printf("%d\n", a);printf("%d\n", b);return 0;
}
7、C++輸入&輸出
? 是 Input Output Stream 的縮寫(xiě),是標(biāo)準(zhǔn)的輸入、輸出流庫(kù),定義了標(biāo)準(zhǔn)的輸入、輸出對(duì)象。
? std::cin 是 istream 類(lèi)的對(duì)象,它主要面向窄字符(narrow characters (of type char))的標(biāo)準(zhǔn)輸 入流。
? std::cout 是 ostream 類(lèi)的對(duì)象,它主要面向窄字符的標(biāo)準(zhǔn)輸出流。
? std::endl 是一個(gè)函數(shù),流插入輸出時(shí),相當(dāng)于插如一個(gè)換行字符加刷新緩沖區(qū)。
現(xiàn)在的階段,std::endl可以理解為就是"\n"
? >是流提取運(yùn)算符。(C語(yǔ)言還用這兩個(gè)運(yùn)算符做位運(yùn)算左移/右移)
? 使用C++輸入輸出更方便,不需要像printf/scanf輸入輸出時(shí)那樣,需要手動(dòng)指定格式,C++的輸入輸出可以自動(dòng)識(shí)別變量類(lèi)型(本質(zhì)是通過(guò)函數(shù)重載實(shí)現(xiàn)的,這個(gè)以后會(huì)講到),其實(shí)最重要的是 C++的流能更好的支持自定義類(lèi)型對(duì)象的輸入輸出。
? IO流涉及類(lèi)和對(duì)象,運(yùn)算符重載、繼承等很多面向?qū)ο蟮闹R(shí),這些知識(shí)我們還沒(méi)有講解,所以這里我們只能簡(jiǎn)單認(rèn)識(shí)一下C++ IO流的用法,后面我們會(huì)有專(zhuān)門(mén)的一個(gè)章節(jié)來(lái)細(xì)節(jié)IO流庫(kù)。
? cout/cin/endl等都屬于C++標(biāo)準(zhǔn)庫(kù),C++標(biāo)準(zhǔn)庫(kù)都放在一個(gè)叫std(standard)的命名空間中,所以要 通過(guò)命名空間的使用方式去用他們。
注意:這里不是說(shuō)std里有iostream,那為什么還要引入頭文件<iostream>,
在C++標(biāo)準(zhǔn)庫(kù)中,iostream
是一個(gè)命名空間,包含了處理輸入輸出操作的主要類(lèi)和函數(shù),如cin
、cout
等。雖然這些功能已經(jīng)包含在庫(kù)中,但是通過(guò)引入#include <iostream>
頭文件,程序員可以明確地告訴編譯器他們想要使用這部分功能,并使得編譯器能在程序中定位到這些相關(guān)的聲明和定義。
頭文件不僅提供類(lèi)和函數(shù)的原型,還可以包含一些預(yù)處理器指令(如宏定義),以及可能導(dǎo)致鏈接階段錯(cuò)誤的其他信息。此外,通過(guò)引用頭文件,我們能享受到編譯器的依賴(lài)管理和類(lèi)型檢查,提高代碼的可讀性和維護(hù)性。
所以,即使庫(kù)內(nèi)部已經(jīng)有了這些內(nèi)容,每使用一次iostream里的功能都需要包含這個(gè)頭文件,這是一種編程約定和組織方式。
? 一般日常練習(xí)中我們可以u(píng)sing namespace std,實(shí)際項(xiàng)目開(kāi)發(fā)中不建議using namespace std。
? VS系列編譯器沒(méi)有包含<stdio.h>,也可以使用printf和scanf,因?yàn)樵?lt;iostream>里間接包含了。其他編譯器可能會(huì)報(bào)錯(cuò),就加上<stdio.h>。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
int main()
{int a = 0;double b = 0.1;char c = 'x';cout << a << " " << b << " " << c << endl;std::cout << a << " " << b << " " << c << std::endl;scanf("%d%lf", &a, &b);printf("%d %lf\n", a, b);// 可以自動(dòng)識(shí)別變量的類(lèi)型cin >> a;cin >> b >> c;cout << a << endl;cout << b << " " << c << endl;return 0;
}
#include<iostream>
using namespace std;
int main()
{// 在io需求?較?的地方,如部分大量輸入的競(jìng)賽題中,加上以下3行代碼// 可以提高C++IO效率ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);return 0;
}
-
ios_base::sync_with_stdio(false)
:取消標(biāo)準(zhǔn)輸入輸出緩沖區(qū)(stdio)與C庫(kù)的標(biāo)準(zhǔn)輸入輸出緩沖區(qū)之間的同步。這可以避免在處理大量數(shù)據(jù)時(shí)由于等待I/O操作完成而導(dǎo)致的性能瓶頸。 -
cin.tie(nullptr)
和cout.tie(nullptr)
:取消 cin 和 cout 對(duì)齊到同一線(xiàn)程的同步。當(dāng)有多線(xiàn)程同時(shí)讀寫(xiě)控制臺(tái)輸出時(shí),這可以避免因?yàn)榫€(xiàn)程間的同步而產(chǎn)生的額外開(kāi)銷(xiāo)。
在需要頻繁輸入輸出、并且對(duì)速度有較高要求的情況下,例如編程競(jìng)賽或大數(shù)據(jù)處理等場(chǎng)景,加入這些代碼有助于提升程序運(yùn)行效率。然而,在日常開(kāi)發(fā)中,如果不需要處理這種高并發(fā)場(chǎng)景,保持默認(rèn)設(shè)置通常是更安全的選擇。
8、缺省參數(shù)
? 缺省參數(shù)是聲明或定義函數(shù)時(shí)為函數(shù)的參數(shù)指定一個(gè)缺省值。在調(diào)用該函數(shù)時(shí),如果沒(méi)有指定實(shí)參則采用該形參的缺省值,否則使用指定的實(shí)參,缺省參數(shù)分為全缺省和半缺省參數(shù)。(有些地方把缺省參數(shù)也叫默認(rèn)參數(shù))
? 全缺省就是全部形參給缺省值,半缺省就是部分形參給缺省值。C++規(guī)定半缺省參數(shù)必須從右往左依次連續(xù)缺省,不能間隔跳躍給缺省值。
? 帶缺省參數(shù)的函數(shù)調(diào)用,C++規(guī)定必須從左到右依次給實(shí)參,不能跳躍給實(shí)參。
? 函數(shù)聲明和定義分離時(shí),缺省參數(shù)不能在函數(shù)聲明和定義中同時(shí)出現(xiàn),規(guī)定函數(shù)聲明處給缺省 值。(考慮到函數(shù)聲明和定義處的缺省值不一致等問(wèn)題)
#include <iostream>
#include <assert.h>using namespace std;
void Func(int a = 0)
{cout << a << endl;
}int main()
{Func(); // 沒(méi)有傳參時(shí),使用參數(shù)的默認(rèn)值 0Func(10); // 傳參時(shí),使用指定的實(shí)參 10return 0;
}
#include <iostream>using namespace std;// 全缺省
void Func1(int a = 10, int b = 20, int c = 30)
{cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl << endl;
}// 半缺省
void Func2(int a, int b = 10, int c = 20)
{cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl << endl;
}int main()
{Func1();Func1(1);Func1(1, 2);Func1(1, 2, 3);Func2(100);Func2(100, 200);Func2(100, 200, 300);return 0;
}
// Stack.h
#include <iostream>
#include <assert.h>using namespace std;typedef int STDataType;
typedef struct Stack
{STDataType* a;int top;int capacity;
}ST;void STInit(ST* ps, int n = 4);// Stack.cpp
#include"Stack.h"// 缺省參數(shù)不能聲明和定義同時(shí)給
void STInit(ST* ps, int n)
{assert(ps && n > 0);ps->a = (STDataType*)malloc(n * sizeof(STDataType));ps->top = 0;ps->capacity = n;
}// test.cpp
#include"Stack.h"int main()
{ST s1;STInit(&s1);// 確定要插入1000個(gè)數(shù)據(jù),初始化時(shí)就開(kāi)好,避免擴(kuò)容ST s2;STInit(&s2, 1000);return 0;
}
9、函數(shù)重載
C++支持在同一作用域中出現(xiàn)同名函數(shù),但是要求這些同名函數(shù)的形參不同,可以是
參數(shù)個(gè)數(shù)不同,參數(shù)類(lèi)型不同,參數(shù)類(lèi)型順序不同。
注意:返回值類(lèi)型不同不能作為重載條件,因?yàn)檎{(diào)用時(shí)也無(wú)法區(qū)分
C語(yǔ)言是不支持同一作用域中出現(xiàn)同名函數(shù)的。
#include<iostream>using namespace std;// 1、參數(shù)類(lèi)型不同
int Add(int left, int right)
{cout << "int Add(int left, int right)" << endl;return left + right;
}double Add(double left, double right)
{cout << "double Add(double left, double right)" << endl;return left + right;
}// 2、參數(shù)個(gè)數(shù)不同
void f()
{cout << "f()" << endl;
}void f(int a)
{cout << "f(int a)" << endl;
}// 3、參數(shù)類(lèi)型順序不同
void f(int a, char b)
{cout << "f(int a,char b)" << endl;
}void f(char b, int a)
{cout << "f(char b, int a)" << endl;
}// 返回值不同不能作為重載條件,因?yàn)檎{(diào)用時(shí)也無(wú)法區(qū)分
//void fxx()
//{}
//
//int fxx()
//{
// return 0;
//}// 下面兩個(gè)函數(shù)構(gòu)成重載
// f1()調(diào)用時(shí),會(huì)報(bào)錯(cuò),存在歧義,編譯器不知道調(diào)用誰(shuí)
void f1()
{cout << "f()" << endl;
}void f1(int a = 10)
{cout << "f(int a)" << endl;
}int main()
{Add(10, 20);Add(10.1, 20.2);f();f(10);f(10, 'a');f('a', 10);return 0;
}
10、引用
10.1?引用的概念和定義
引用不是新定義一個(gè)變量,而是給已存在變量取了一個(gè)別名,編譯器不會(huì)為引用變量開(kāi)辟內(nèi)存空間, 它和它引用的變量共用同一塊內(nèi)存空間。
類(lèi)型& 引用別名 = 引用對(duì)象;
#include<iostream>
using namespace std;
int main()
{int a = 0;// 引用:b和c是a的別名int& b = a;int& c = a;// 也可以給別名b取別名,d相當(dāng)于還是a的別名int& d = b;++d;// 這里取地址我們看到是?樣的cout << &a << endl;cout << &b << endl;cout << &c << endl;cout << &d << endl;return 0;
}
10.2 引用的特性
? 引用在定義時(shí)必須初始化
? 一個(gè)變量可以有多個(gè)引用
? C++規(guī)定引用不能改變指向,引用一旦引用一個(gè)實(shí)體,再不能引用其他實(shí)體
#include<iostream>using namespace std;int main()
{int a = 10;// 編譯報(bào)錯(cuò):“ra”: 必須初始化引?//int& ra;int& b = a;int c = 20;// 這里并不是讓b引用c,因?yàn)镃++規(guī)定引用不能改變指向// 這里是?個(gè)賦值,即 a = 20;b = c;cout << &a << endl;cout << &b << endl;cout << &c << endl;return 0;
}
10.3 引用的使用
? 引用在實(shí)踐中主要是于引用傳參和引用做返回值中減少拷貝提高效率和改變引用別名時(shí)同時(shí)改變引用對(duì)象。
? 引用傳參跟指針傳參功能是類(lèi)似的,引用傳參相對(duì)更方便一些。
? 引用返回值的場(chǎng)景相對(duì)比較復(fù)雜,我們?cè)谶@里簡(jiǎn)單講了一下場(chǎng)景,還有一些內(nèi)容后續(xù)類(lèi)和對(duì)象章節(jié)中會(huì)繼續(xù)深入講解。
? 引用和指針在實(shí)踐中相輔相成,功能有重疊性,但是各有特點(diǎn),互相不可替代(如:在鏈表里的next,如果用引用,因?yàn)椴荒芨淖兤渲赶?#xff0c;所以不能改變next,只能用指針)。C++的引用跟其他語(yǔ)言的引用(如Java)是有很大的區(qū)別的,除了用法,最大的區(qū)別就是是,C++引用定義后不能改變指向, Java的引用可以改變指向。
? 一些主要用C代碼實(shí)現(xiàn)版本數(shù)據(jù)結(jié)構(gòu)教材中,使用C++引用替代指針傳參,目的是簡(jiǎn)化程序,避開(kāi)復(fù)雜的指針。
#include<iostream>using namespace std;void Swap(int& rx, int& ry)
{int tmp = rx;rx = ry;ry = tmp;
}int main()
{int x = 0, y = 1;cout << x << " " << y << endl;Swap(x, y);cout << x << " " << y << endl;return 0;
}
#include<iostream>using namespace std;typedef int STDataType;
typedef struct Stack
{STDataType* a;int top;int capacity;
}ST;void STInit(ST& rs, int n = 4)
{rs.a = (STDataType*)malloc(n * sizeof(STDataType));rs.top = 0;rs.capacity = n;
}// 棧頂
void STPush(ST& rs, STDataType x)
{// 滿(mǎn)了, 擴(kuò)容if (rs.top == rs.capacity){printf("擴(kuò)容\n");int newcapacity = rs.capacity == 0 ? 4 : rs.capacity * 2;STDataType* tmp = (STDataType*)realloc(rs.a, newcapacity *sizeof(STDataType));if (tmp == NULL){perror("realloc fail");return;}rs.a = tmp;rs.capacity = newcapacity;}rs.a[rs.top] = x;rs.top++;
}// int STTop(ST& rs)
int& STTop(ST& rs)
{return rs.a[rs.top - 1];
}int main()
{ST st1;STInit(st1);STPush(st1, 1);STPush(st1, 2);cout << STTop(st1) << endl;// 2STTop(st1) += 10;// 返回了rs.a[rs.top - 1]的別名,可以進(jìn)行修改cout << STTop(st1) << endl;// 12return 0;
}
#include<iostream>
#include<assert.h>using namespace std;
typedef struct SeqList
{int a[10];int size;
}SLT;void SeqPushBack(SLT& sl, int x)
{}typedef struct ListNode
{int val;struct ListNode* next;
}LTNode, * PNode;// 指針變量也可以取別名,這里L(fēng)TNode*& phead就是給指針變量取別名
// 這樣就不需要用?級(jí)指針了,相對(duì)而言簡(jiǎn)化了程序
//void ListPushBack(LTNode** phead, int x)
//void ListPushBack(LTNode*& phead, int x)
void ListPushBack(PNode& phead, int x)
{PNode newnode = (PNode)malloc(sizeof(LTNode));assert(newnode);newnode->val = x;newnode->next = NULL;if (phead == NULL){phead = newnode;}else{//...}
}int main()
{PNode plist = NULL;ListPushBack(plist, 1);return 0;
}
10.4 const引用
? 可以引用一個(gè)const對(duì)象,但是必須用const引用。const引用也可以引用普通對(duì)象,因?yàn)閷?duì)象的訪(fǎng)
問(wèn)權(quán)限在引用過(guò)程中可以縮小,但是不能放大。
? 不需要注意的是類(lèi)似 int& rb = a * 3; double d = 12.34; int& rd = d; 這樣一些場(chǎng)景下a * 3的結(jié)果保存在一個(gè)臨時(shí)對(duì)象中, int& rd = d 也是類(lèi)似,在類(lèi)型轉(zhuǎn)換中會(huì)產(chǎn)生 存儲(chǔ)中間值的臨時(shí)對(duì)象,也就是,rb和rd引用的都是臨時(shí)對(duì)象,而C++規(guī)定臨時(shí)對(duì)象具有常性(只能讀,不能改),所以這里就觸發(fā)了權(quán)限放大,必須要用const引用才可以。
? 所謂臨時(shí)對(duì)象就是編譯器需要一個(gè)空間暫存表達(dá)式的求值結(jié)果時(shí)而臨時(shí)創(chuàng)建的一個(gè)未命名的對(duì)象,C++中把這個(gè)未命名對(duì)象叫做臨時(shí)對(duì)象。
??C++中的臨時(shí)對(duì)象除了上述的 表達(dá)式求值,類(lèi)型轉(zhuǎn)換,還有以下兩種:
? ? ?1. 函數(shù)返回值:當(dāng)你調(diào)用一個(gè)函數(shù)并且它的返回類(lèi)型不是引用或指針時(shí),會(huì)生成一個(gè)臨時(shí)對(duì)象來(lái)存儲(chǔ)返回值。
int x = getSomeValue(); // getSomeValue() 返回一個(gè)新的整數(shù),x 是對(duì)該臨時(shí)對(duì)象的引用
? ? 2. 參數(shù)傳遞:在函數(shù)參數(shù)列表中,如果傳遞的是非引用類(lèi)型的值,也會(huì)創(chuàng)建臨時(shí)對(duì)象來(lái)存儲(chǔ)實(shí)際傳入的數(shù)據(jù)。
void func(int temp) { // temp 是對(duì)一個(gè)臨時(shí)變量的引用 }
func(42); // 內(nèi)部創(chuàng)建了一個(gè)臨時(shí)變量來(lái)作為func的參數(shù)
int main()
{const int a = 10;// 編譯報(bào)錯(cuò):error C2440: “初始化”: ?法從“const int”轉(zhuǎn)換為“int &”// 這里的引用是對(duì)a訪(fǎng)問(wèn)權(quán)限的放?//int& ra = a;// 這樣才可以const int& ra = a;// 編譯報(bào)錯(cuò):error C3892: “ra”: 不能給常量賦值//ra++;// 這里的引用是對(duì)b訪(fǎng)問(wèn)權(quán)限的縮?int b = 20;const int& rb = b;// 編譯報(bào)錯(cuò):error C3892: “rb”: 不能給常量賦值//rb++;return 0;
}
#include<iostream>using namespace std;int main()
{int a = 10;const int& ra = 30;// 編譯報(bào)錯(cuò): “初始化”: 無(wú)法從“int”轉(zhuǎn)換為“int &”// 臨時(shí)對(duì)象具有常性// int& rb = a * 3;const int& rb = a * 3;double d = 12.34;// 編譯報(bào)錯(cuò):“初始化”: ?法從“double”轉(zhuǎn)換為“int &”// 臨時(shí)對(duì)象具有常性// int& rd = d;const int& rd = d;return 0;
}
10.5 指針和引用的關(guān)系(面試高頻考查點(diǎn))
C++中指針和引用就像兩個(gè)性格迥異的親兄弟,指針是哥哥,引用是弟弟,在實(shí)踐中相輔相成,功能有重疊性,但是各有特點(diǎn),互相不可替代(如:在鏈表里的next,如果用引用,因?yàn)椴荒芨淖兤渲赶?#xff0c;所以不能改變next,只能用指針)。
? 語(yǔ)法概念上引用是一個(gè)變量的取別名不開(kāi)空間,指針是存儲(chǔ)一個(gè)變量地址,要開(kāi)空間。
? 引用在定義時(shí)必須初始化,指針可以不初始化。
? C++規(guī)定引用不能改變指向,引用在初始化時(shí)引用一個(gè)對(duì)象后,就不能再引用其他對(duì)象;而指針可以在不斷地改變指向?qū)ο蟆?/p>
? 引用可以直接訪(fǎng)問(wèn)指向?qū)ο?/strong>,指針需要解引用才是訪(fǎng)問(wèn)指向?qū)ο蟆?/p>
? sizeof中含義不同,引用結(jié)果為引用類(lèi)型的大小,但指針始終是地址空間所占字節(jié)個(gè)數(shù)(32位平臺(tái)下 占4個(gè)字節(jié),64位下是8byte) ? 指針很容易出現(xiàn)空指針和野指針的問(wèn)題,引用很少出現(xiàn),相對(duì)更安全一些。 ? inline修飾的函數(shù)叫做內(nèi)聯(lián)函數(shù),編譯時(shí)C++編譯器會(huì)在調(diào)用的地方展開(kāi)內(nèi)聯(lián)函數(shù),這樣調(diào)用內(nèi)聯(lián) 函數(shù)就不需要建立棧幀了,就可以提高效率。 ? inline對(duì)于編譯器而言只是一個(gè)建議,也就是說(shuō),你加了inline編譯器也可以選擇在調(diào)用的地方不展開(kāi),不同編譯器關(guān)于inline什么情況展開(kāi)各不相同,因?yàn)镃++標(biāo)準(zhǔn)沒(méi)有規(guī)定這個(gè)。inline適用于頻繁調(diào)用的短小函數(shù),對(duì)于遞歸函數(shù),代碼相對(duì)多一些的函數(shù),加上inline也會(huì)被編譯器忽略。 ? C語(yǔ)言實(shí)現(xiàn)宏函數(shù)也會(huì)在預(yù)處理時(shí)替換展開(kāi),但是宏函數(shù)實(shí)現(xiàn)很復(fù)雜很容易出錯(cuò)的,且不方便調(diào) 試,C++設(shè)計(jì)了inline目的就是替代C的宏函數(shù)。 ? vs編譯器 debug版本下面默認(rèn)是不展開(kāi)inline的,這樣方便調(diào)試,debug版本想展開(kāi)需要設(shè)置?下 以下兩個(gè)地方。 ? inline不建議聲明和定義分離到兩個(gè)文件,分離會(huì)導(dǎo)致鏈接錯(cuò)誤。因?yàn)閕nline被展開(kāi),就沒(méi)有函數(shù)地址,鏈接時(shí)會(huì)出現(xiàn)報(bào)錯(cuò),建議直接聲明處定義。 NULL實(shí)際是一個(gè)宏,在傳統(tǒng)的C頭文件(stddef.h)中,可以看到如下代碼: ? C++中NULL可能被定義為字面常量0,或者C中被定義為無(wú)類(lèi)型指針(void*)的常量。不論采取何種定義,在使用空值的指針時(shí),都不可避免的會(huì)遇到一些麻煩,如: 在C語(yǔ)言中,由于類(lèi)型檢查不嚴(yán)格,類(lèi)型都可以轉(zhuǎn)換,f(NULL),兩個(gè)重載函數(shù)都可以調(diào)用,報(bào)錯(cuò) 在C++中,本想通過(guò)f(NULL)調(diào)用指針版本的 f(int*)函數(shù),但是由于NULL被定義成0,調(diào)用了f(int x),因此與程序的初衷相悖。f((void*)NULL); 調(diào)用會(huì)報(bào)錯(cuò)(類(lèi)型檢查嚴(yán)格)。 ? C++11中引入了nullptr,nullptr是一個(gè)特殊的關(guān)鍵字,nullptr是一種特殊類(lèi)型的字面量,它可以轉(zhuǎn)換成任意其他類(lèi)型的指針類(lèi)型。 nullptr只能被隱式地轉(zhuǎn)換為指針類(lèi)型,而不能被轉(zhuǎn)換為整數(shù)類(lèi)型。11、inline
#include<iostream>using namespace std;inline int Add(int x, int y)
{int ret = x + y;ret += 1;ret += 1;ret += 1;return ret;
}
int main()
{// 可以通過(guò)匯編觀察程序是否展開(kāi)// 有call Add語(yǔ)句就是沒(méi)有展開(kāi),沒(méi)有就是展開(kāi)了int ret = Add(1, 2);cout << Add(1, 2) * 5 << endl;return 0;
}
// F.h
#include <iostream>using namespace std;inline void f(int i);// F.cpp
#include "F.h"void f(int i)
{cout << i << endl;
}// main.cpp
#include "F.h"int main()
{// 鏈接錯(cuò)誤:無(wú)法解析的外部符號(hào) "void __cdecl f(int)" (?f@@YAXH@Z)f(10);return 0;
}
12、nullptr
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
#include<iostream>using namespace std;void f(int x)
{cout << "f(int x)" << endl;
}void f(int* ptr)
{cout << "f(int* ptr)" << endl;
}int main()
{f(0); // f(int x)f(NULL); // f(int x)f((int*)NULL); // 強(qiáng)制轉(zhuǎn)換 f(int* ptr)// 編譯報(bào)錯(cuò):error C2665: “f”: 2 個(gè)重載函數(shù)中沒(méi)有一個(gè)匹配的參數(shù)類(lèi)型// f((void*)NULL);f(nullptr); //f(int* ptr)return 0;
}