【C++快速入門】C++常用語法大總結(快速入門,適用有一定語言基礎的人,不包含面向對象)

課程推薦

這裏推薦一門課,30小時快速精通C++和外掛實戰,名字看着有點浮誇。。。但是內容真不是吹的,這裏的總結很大一部分是對於課堂內容的理解與消化。

由於C++內容太多了,這一篇文章不包含面向對象。

語法須知

  • C++的源文件拓展名:cpp(c plus plus 的簡稱)
  • C++程序的入口是main函數(函數即方法,一個意思)
  • C++完全兼容C語言的語法,很久以前,C++叫做C with classes

cin、cout

  • C++中常使用 cin、cout 進行控制檯的輸入、輸出
  • endl 是換行的意思
#include<iostream>
using namespace std; 
int main(){
	int age; 
	cin >> age;
	cout << "age is" << age << end1; 
	return 0;
}

函數重載(Overload)

C語言不支持函數重載,C++支持函數重載。

  • 規則
    • 函數名相同
    • 參數個數不同、參數類型不同、參數順序不同
  • 注意
    • 返回值類型與函數重載無關
    • 調用函數時,實參的隱式類型轉換可能會產生二義性

有面向對象基礎的話,應該知道個七七八八。
具體可以看看下面幾段代碼,體會一下。

#include <iostream>
using namespace std;

// 參數個數不同
int sum(int v1, int v2) {
	return v1 + v2;
}
int sum(int v1, int v2, int v3) {
	return v1 + v2 + v3;
}

int main() {
	cout << sum(10, 20) << endl; // 30
	cout << sum(10, 20, 30) << endl; // 60
	return 0;
}
#include <iostream>
using namespace std;

// 參數類型不同
void func(int v1, double v2) {
	cout << "func(int v1, double v2)" << endl;
}
void func(double v1, int v2) {
	cout << "func(double v1, int v2)" << endl;
}

int main() {
	func(10, 10.5);  // func(int v1, double v2)
	func(10.5, 10);  // func(double v1, int v2)	
	return 0;
}

返回值類型與函數重載無關,爲什麼有這個設定呢?
因爲有時候會產生二義性,具體看下面這段代碼。

#include <iostream>
using namespace std;

// 歧義,二義性
int func() {
	cout << "int func()" << endl;
	return 1;
}
double func() { // 會報錯,函數返回值不同
	cout << "double func()" << endl;
	return 2.5;
}

int main(){
	func(); // 不知道該調用哪個函數,自然不能構成重載
}

  • 本質
    • 採用了name mangling或者叫name decoration技術
    • C++編譯器默認會對符號名(比如函數名)進行改編、修飾,有些地方翻譯爲“命名傾軋"
    • 重載時會生成多個不同的函數名,不同編譯器(MSVC、g++)有不同的生成規則
    • 通過IDA打開【VS Release禁止優化】可以看到
#include <iostream>
using namespace std;
// name mangling 技術
// C++編譯器默認會對符號名(比如函數名)進行改編、修飾
// 重載時會生成多個不同的函數名,不同編譯器(MSVC、g++)有不同的生成規則

// 命名規則與下面不一定相同,但是會產生多個函數名

// display_v
void display() {
	cout << "display()" << endl;
}

// display_i
void display(int a) {
	cout << "display(int) - " << a << endl;
}

// display_l
void display(long a) {
	cout << "display(long) - " << a << endl;
}

// display_d
void display(double a) {
	cout << "display(double) - " << a << endl;
}

默認參數

  • C++允許函數設置默認參數,在調用時可以根據情況省略實參。規則如下:
    • 默認參數只能按照右到左的順序
    • 如果函數同時有聲明、實現,默認參數只能放在函數聲明中
    • 默認參數的值可以是常量、全局符號(全局變量、函數名)
  • 如果函數的實參經常是同一個值,可以考慮使用默認參數
#include <iostream>
using namespace std;

int age = 33; 

void test() {
	cout << "test()" << endl;
}
// 函數指針的默認參數爲test()函數
void display(int a = 11, int b = 22, int c = age, void(*func)() = test){
	cout << "a is " << a << endl; 
	cout << "b is " << b << endl;
	cout << "c is " << c << endl; 
	test();
}
int main(){
    // 如果不傳參,會調用默認參數
	display();
    // display(11, 22, age, test); // 與上面完全一樣
	return 0;
}
a is 11
b is 22
c is 33
test()
  • 函數重載、默認參數可能會產生衝突、二義性(建議優先選擇使用默認參數)
void display(int a, int b=20){ // 有默認參數
	cout<<"a is"<<a<<endl; 
}
void display(int a){ //與上面的函數產生二義性
	cout<<"a is"<<a<<endl; 
}
int main(){
	display(10); // 不知道該調用哪個函數,報錯
	return 0;
}

extern “C”

  • extern "C" 修飾的代碼會按照C語言的方式去編譯
extern "C" void func(){
	cout << "func()" <<end1; 
}
extern "C" void func(int age){
	cout << "func(int age)" << age << end1;
}
extern "C" {
	void func(){
		cout << "func()" <<end1; 
	}
	void func(int age){
		cout << "func(int age)" << age << end1;
	}
}
  • 如果函數同時有聲明和實現,要讓函數聲明被 extern “C” 修飾,函數實現可以不修飾
extern "C" void func();  // 函數聲明必須被 extern "C" 修飾
extern "C" void func(int age); 

void func(){ // 函數實現可以不被修飾
	cout << "func()" << end1; 
}
void func(int age){
cout << "func(int age)" << age << end1;
}
extern"C"{
	void func(); 
	void func(int age); 
}
void func(){
	cout << "func()" << end1; 
}
void func(int age){
	cout << "func(int age)" << age << end1;
}

上面兩段代碼完全等價。

由於C、C++編譯規則的不同,在C、C++混合開發時,可能會經常出現以下操作 :

  • C++在調用C語言API時,需要使用 extern "C" 修飾C語言的函數聲明
    在這裏插入圖片描述

有時也會在編寫C語言代碼中直接使用extern “C”,這樣就可以直接被C++調用

  • 通過使用宏 _cplusplus 來區分C、C++環境
    在這裏插入圖片描述

#pragma once

  • 我們經常使用 #ifndef、#define、#endif 來防止頭文件的內容被重複包含
  • #pragma once 可以防止整個文件的內容被重複包含
  • 區別
    • #ifndef、#define、#endif 受 CC++ 標準的支持,不受編譯器的限制
    • 有些編譯器不支持 #pragma once(較老編譯器不支持,如GCC3.4版本之前),兼容性不夠好
    • #ifndef、#define、#endif 可以針對一個文件中的部分代碼,而 #pragma once 只能針對整個文件

內聯函數(inline function)

  • 使用 inline 修飾函數的聲明或者實現,可以使其變成內聯函數

    • 建議聲明和實現都增加 inline 修飾
  • 特點

    • 編譯器會將函數調用直接展開爲函數體代碼
    • 可以減少函數調用的開銷
    • 會增大代碼體積
  • 注意
    • 儘量不要內聯超過 10 行代碼的函數
    • 有些函數即使聲明爲 inline,也不一定會被編譯器內聯,比如遞歸函數

內聯函數與宏

  • 內聯函數和宏,都可以減少函數調用的開銷
  • 對比宏,內聯函數多了語法檢測和函數特性

什麼叫函數特性呢?比較下面兩個例子就知道了。

#include<iostream>
#using namespace std;
#define sum(x) (x + x)
int main(){
	int a = 10;
    cout << sum(a++) << endl; // 21
    // 這裏的實際運算是: ((a++) + (a++))
}
#include<iostream>
#using namespace std;
inline int sum(int x){
	return x + x;
}
int main(){
	int a = 10;
	cout << sum(a++) << endl; // 20
	// 這裏的實際運算是: (a + a),然後 a += 1
}

被賦值的表達式

  • C++的有些表達式是可以被賦值的
int a = 1;
int b = 2;
(a=b) = 3; 		//賦值給了a
(a<b?a:b) = 4;	//賦值給了b

const

  • const 是常量的意思,被其修飾的變量的值不可修改
  • 如果修飾的是類、結構體(的指針),其成員也不可以更改

觀察一下以下指針分別是什麼含義?

int age = 10; 	
const int *p1 = &age;	// p1不是常量,*p1是常量
int const *p2 = &age;   // p2不是常量,*p2是常量
int * const p3 = &age;  // p3是常量,*p3不是常量
const int * const p4 = &age;  // p4是常量,*p4也是常量
int const * const p5 = &axge; // p5是常量,*p5也是常量
  • 指針問題可以用以下的結論來解決:
    const 修飾的是其右邊的內容

在這裏插入圖片描述

#include<iostream>
using namespace std;
int main(int argc, char *agrv[]) {

	struct Date {
		int year;
		int month;
		int day;
	};

	Date d1 = { 2020, 2, 17 };
	Date d2 = { 2022, 4, 28 };
	Date *p = &d1;
	*p = d2;
	cout << d1.year << endl;  // 普通對象的訪問方式
	cout << p->month << endl; // 指針變量的訪問方式
	cout << (*p).day << endl; // 指針變量的訪問方式
		
	int age = 10;
	int height = 20;
	// p1不是常量,*p1是常量
	const int * p1 = &age;
	// p2不是常量,*p2是常量
	int const * p2 = &age;
	// p3是常量,*p3不是常量
	int * const p3 = &age;
	// p4是常量,*p5也是常量
	const int * const p4 = &age;
	// p5是常量,*p5也是常量
	int const * const p5 = &age;
	// p5 = &height; // 報錯,無法修改
	// *p5 = 20; // 報錯,無法修改
	
	return 0;
}

引用(Reference)

  • 在C語言中,使用 指針(Pointer) 可以間接獲取、修改某個變量的值
  • 在C++中,使用 引用(Reference) 可以起到跟指針類似的功能
int age = 20;
int &rage = age; // rage是一個引用
  • 注意點
    • 引用相當於是變量的別名(基本數據類型、枚舉、結構體、類、指針、數組等,都可以有引用)
    • 對引用做計算,就是對引用所指向的變量做計算
    • 在定義的時候就必須初始化,一旦指向了某個變量,就不可以再改變,“從一而終"
    • 可以利用引用初始化另一個引用,相當於某個變量的多個別名
    • 不存在【引用的引用、指向引用的指針、引用數組】
  • 引用存在的價值之一:比指針更安全、函數返回值可以被賦值

引用的本質

  • 引用的本質就是指針,只是編譯器削弱了它的功能,所以引用就是弱化了的指針
  • 一個引用佔用一個指針的大小

普通變量的引用

#include<iostream>
using namespace std;

int main(){
	int age = 10;

	// 定義了一個age的引用,ref相當於是age的別名
	int &ref = age;
	// 注意,這不是引用的引用,此時的ref就相當於age(別名)
	int &ref1 = ref; // 所以ref1依舊是age的引用
	int &ref2 = ref1; // ref2也是age的引用

	ref += 10; 	// age += 10; 
	ref1 += 10; // age += 10;
	ref2 += 10; // age += 10;

	cout << age << endl; // 40
}

引用作爲函數的參數

先來看一段我們熟悉的代碼。

#include<iostream>
using namespace std;

void swap(int v1, int v2){ 
    int tmp = v1;
    v1 = v2;
    v2 = tmp;
}
int main(){
	int a = 10;
	int b = 20;
	swap(a, b); // a=10, b=20
	cout << "a = " << a << ", b = " << b << endl;
}

我們都知道,這樣做不會真正改變 a,b 的值。

那麼改成下面這樣呢?

#include<iostream>
using namespace std;

void swap(int *v1, int *v2){ 
    int tmp = *v1;
    *v1 = *v2;
    *v2 = tmp;
}
int main(){
	int a = 10;
	int b = 20;
	swap(&a, &b); // a=20, b=10
	cout << "a = " << a << ", b = " << b << endl;
}

這樣寫明顯是可以的。
其實用引用也可以達到同樣的效果,而且更簡潔。

#include<iostream>
using namespace std;

void swap(int &v1, int &v2) {
	int tmp = v1;
	v1 = v2;
	v2 = tmp;
}
int main(){
	int a = 10;
	int b = 20;
	// 不需要 int &ref = a; int &ref2 = b; swap(ref1, ref2);
	swap(a, b); // 10, 20
	cout << "a = " << a << ", b = " << b << endl;
}

結構體的引用

#include<iostream>
using namespace std;

struct Date{
	int year;
	int month;
	int day;
};

int main(){
	// 結構體的引用
	Date date = {2022, 2, 2};
	Date &ref1 = date; // 引用
	ref1.year = 2023;  // 通過引用修改了值
	cout << date.year << endl; // 值發生了變化
	return 0;
}

指針的引用

#include<iostream>
using namespace std;

int main(){
	// 指針的引用
	int age = 10;
	int *p1 = &age;
	int *&ref2 = p; // 引用
	// 下面三種賦值是等價的
	// 通過普通變量,指針,引用都可以修改值
	cout << (age=30) << endl;
	cout << (*p1=40) << endl;
	cout << (*ref2=50) << endl;
	return 0;
}

數組的引用

#include<iostream>
using namespace std;

int main(){
	// 數組的引用
	int *p2;
	// 指針數組,數組裏面可以存放3個int型指針
	int *arr1[3] = { p, p, p }; // 是數組
	// 用於指向數組的指針,和上面不一樣
	int(*arr2)[3]; 	// 是指針
	
	// 數組的引用(引用的本質是指針,所以與指向數組的指針類似)
	int array[] = { 1, 2, 3 };
	int(&ref)[3] = array; 	// 需要寫死數組長度爲,此處爲3
	int * const &ref2 = array; // 不需要寫死數組長度的引用

	return 0;
}

函數返回值被賦值

  • 引用存在的價值之一:比指針更安全、函數返回值可以被賦值
#include<iostream>
using namespace std;

/*
// 無法構成重載
int sum(int v1, int v2) {
	cout << "sum(int v1, int v2)" << endl;
	return v1 + v2;
}
*/		
int sum(int &v1, int &v2) {
	cout << "sum(int &v1, int &v2)" << endl;
	return v1 + v2;
}
int sum(const int &v1, const int &v2) {
	cout << "sum(const int &v1, const int &v2)" << endl;
	return v1 + v2;
}

int main() {	
	// 非const實參
	int a = 10;
	int b = 20;
	sum(a, b);   // sum(int &v1, int &v2)
	
	// const實參
	const int c = 10;
	const int d = 20;
	sum(c, d);   // sum(const int &v1, const int &v2)
	
	sum(10, 20); // sum(const int &v1, const int &v2)

	return 0;
}

常引用(Const Reference)

  • 引用可以被 const 修飾,這樣就無法通過引用修改數據了,可以稱爲常引用

  • const 必須寫在 & 符號的左邊,才能算是常引用

  • const 引用 的特點

    • 可以指向臨時數據(常量、表達式、函數返回值等)
    • 可以指向不同類型的數據
    • 作爲函數參數時(此規則也適用於 const 指針
      • 可以接受const和非const實參(非const引用,只能接受非const實參)
      • 可以跟非const引用構成重載
  • 當常引用指向了不同類型的數據時,會產生臨時變量,即引用指向的並不是初始化時的那個變量

#include<iostream>
using namespace std;

int main(){

	int height = 20;
	int age = 10;
	// ref1不能修改指向,但是可以通過ref1間接修改指向的變量
	int & const ref1 = age; // 寫不寫 const 其實效果一樣
	ref1 = 20;  // 可以通過ref1修改age的值

	// ref2不能修改指向,不可以通過ref2簡潔修改指向的變量
	int const &ref2 = age; // 常引用
	// ref2 = 20; // 無法通過引用修改age的值,報錯

	// p1不能修改指向,可以利用p1間接修改所指向的常量
	int * const p1 = &age; // 實際上這就是引用
	// p1 = &height; // p1不能修改指向	
	*p1 = 20;

	// p2可以修改指向,不可以利用p2間接修改所指向的常量
	int const *p2 = &age;
	p2 = &height; // 可以修改指向
	// *p2 = 20; // 不可以修改age的值,報錯
	
	// 常引用可以指向臨時數據()
	const int &ref1 = 30;
	// int &ref2 = 50; 普通引用不可以
	
	return 0;
}

常引用指向不同類型的數據

  • 當常引用指向了不同類型的數據時,會產生臨時變量,即引用指向的並不是初始化時的那個變量

這是什麼意思呢?我們先來看一段代碼。

#include<iostream>
using namespace std;

int main() {

	int age = 10;
	const int &rAge = age;
	age = 40;
	cout << "age=" << age << endl;
	cout << "rAge=" << rAge << endl;
	
	return 0;
}
age=40
rAge=40

這是一段正常的引用,輸出的結果也在意料之內。

再看下面這段引用。

#include<iostream>
using namespace std;

int main() {

	int age = 10;
	const long &rAge = age; // 注意,這裏是 long 類型
	age = 40; // 由於指向了不同的類型,其實修改的是一個臨時變量
	cout << "age=" << age << endl; // 即,age 的值依舊是10,沒有變
	cout << "rAge=" << rAge << endl; // rAge 的指是一個臨時變量的值
	
	return 0;
}
age=10
rAge=40

體會一下上面兩段代碼的區別。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章