2021-07-12:C++基礎知識01

C++基礎知識01

Section01:雙冒號運算符與做用域

Subsection-1.1:做用域

在C/C++的做用域中,講究一個就近原則。也就是說,在局部和全局都對某個變量進行了定義時,咱們的訪問/調用會去操做 「最近」 的那個變量!例以下面的例子:ios

#include <iostream>

using std::cout;
using std::endl;

int atk = 200;

void test() {
    int atk = 100;
    cout << "Local atk = " << atk << endl;
}

int main() {
    test();
    return 0;
}

你們能夠先猜一下運行的結果?沒錯,就是100!這就是體現了剛剛提到的 「就近原則」。那麼,若是在這種狀況下,咱們仍是想要去讀甚至寫全局的變量atk呢?怎麼辦呢?看看下面的程序:程序員

#include <iostream>

using std::cout;
using std::endl;

int atk = 200;

void test() {
    int atk = 100;
    cout << "Local atk = " << atk << endl;
    cout << "Global at = " << ::atk << endl;
    ::atk = 800;
    cout << "Global at = " << ::atk << endl;
}

int main() {
    test();
    return 0;
}

而後再看一下運行效果:編程

在這裏插入圖片描述
這就是雙冒號運算符,改變做用域的效果。在沒有指定命名空間的時候,做用域就是全局做用域!ide

Subsection-1.2:雙冒號運算符使用命名空間

所謂命名空間,是管理命名列表而生的。之因此須要對命名進行管理,那是爲了在 「迭代開發」 或者是多人 「合做開發」 中,避免 「命名衝突」 的危害!函數

如何調用命名空間的變量和函數呢?那就是用雙冒號做用域限定符!看一個需求:假設在遊戲開發中,先開發了英雄露娜的操做,例如:***、大招;隨後又開發了英雄瀾的操做,其中也有***和大招,請問怎麼實現比較好呢?給出示例程序:測試

(1)先看看開發Lunar的過程spa

頭文件(實現定義,注意定義就要在命名空間裏定義):設計

#pragma once
#ifndef __LUNAR__
#define __LUNAR__

#include <iostream>
using namespace std;

namespace lunar {
	void goAtk();

	void goUltimate();
}

#endif

CPP文件(負責實現):code

#include "Lunar.h"

void lunar::goAtk() {
	cout << "Lunar 釋放***!" << endl;
}

void lunar::goUltimate() {
	cout << "Lunar 釋放大招!" << endl;
}

(2)隨後開發Lan的過程blog

頭文件:

#pragma once
#ifndef __LAN__
#define __LAN__

#include <iostream>
using namespace std;

namespace lan {
	void goAtk();

	void goUltimate();
}

#endif

CPP文件:

#include "Lan.h"

void lan::goAtk() {
	cout << "Lan 釋放***!" << endl;
}

void lan::goUltimate() {
	cout << "Lan 釋放大招!" << endl;
}

總結命名空間:

命名空間能夠存放C++任何名稱,例如變量、結構體、類、函數等等……,而且,命名空間無需加分號!可是命名空間必須放到全局做用域!不能夠寫入任何一個函數、類!

舉一個更簡單的例子,要訪問兩個命名空間的變量和函數:

#include <iostream>

using std::cout;
using std::endl;

namespace ns1 {
    int a;

    void func(int a) {
        cout << a << endl;
    }
}

namespace ns2 {
    int a;

    void func(int a) {
        cout << a << endl;
    }
}

int main() {
    ns1::a = 100;
    ns2::a = 200;
    ns1::func(ns2::a);
    ns2::func(ns1::a);
    return 0;
}

它輸出的結果,就顯而易見了,是:

200
100

Subsection-1.3:命名空間的編程技法總結

在上面,咱們瞭解了,命名空間的設計目的、設計場景。以及以遊戲的迭代開發來了解命名空間的使用!以及簡單的例子,瞭解到命名空間能夠加入任意C++中的名稱元素(變量、函數、類、結構體等……)。可是,命名空間仍是其餘的一些有用的技法:

技法1:可擴充的命名空間

以遊戲開發爲例,瀾這個英雄以前有普通***、放大招的功能,如今要更新一個回城的功能。而此前的代碼不想改動了,能否擴充命名空間呢?以下:

新的的頭文件:

#pragma once
#ifndef __LAN__
#define __LAN__

#include <iostream>
using namespace std;

namespace lan {
	void goAtk();

	void goUltimate();
}

namespace lan {
	void goHome();
}

#endif

新的CPP文件:

#include "Lan.h"

void lan::goAtk() {
	cout << "Lan 釋放***!" << endl;
}

void lan::goUltimate() {
	cout << "Lan 釋放大招!" << endl;
}

void lan::goHome() {
	cout << "Lan 回城!" << endl;
}

測試程序:

#include "Lan.h"

int main() {
	lan::goAtk();
	lan::goUltimate();
	lan::goHome();
	return 0;
}

運行後的結果:
在這裏插入圖片描述
總結:
命名空間能夠擴展,每次擴展不是覆蓋原有的命名空間,而是把舊的命名空間和新的命名空間合併!

技法2:可匿名的命名空間

這個技法其實沒啥大用,就怕有的程序員使用了,而你不知道,就會踩坑!

若是命名空間寫爲:

namespace {
	int a, b;
}

實際上等價於在當前文件,寫了:

static int a;
static int b;

就徹底等價於靜態變量/函數,意味着只能在當前文件裏使用了!好比下面的程序:

#include <iostream>
using namespace std;

namespace {
	void sayHello() {
		cout << "Hello World" << endl;
	}
}

int main() {
	sayHello();
	return 0;
}

運行的結果是:
在這裏插入圖片描述

技法3:可嵌套的命名空間

命名空間是能夠嵌套的!這通常得是很大的項目了!不然,其實命名空間自己也是一個比較大,我的認爲是 「僅次於全局做用域」 的存在了!例如:

#include <iostream>
using namespace std;

namespace ns1 {
	int a = 1;

	namespace ns2 {
		int a = 2;
	}
}

int main() {
	cout << ns1::a << ' ' << ns1::ns2::a << endl;
	return 0;
}

運行的結果是:

在這裏插入圖片描述

技法4:存在別名的命名空間

這個其實很簡單,只須要用一個新的namespace空間名去賦值一箇舊的命名空間便可,例如:

#include <iostream>
using namespace std;

namespace ns1 {
	int a = 1;

	namespace ns2 {
		int a = 2;
	}
}

int main() {

	namespace newNS = ns1;

	cout << newNS::a << ' ' << ns1::ns2::a << endl;
	return 0;
}

而後輸出的結果,和上一個技法的例子,輸出一致!

Section02:using關鍵字用法

Subsection-2.1:用using定義類型別名

對比C語言的 #define 以及 typedef

若是用 #define,則,其規則是:

#define 別名 所表明的名稱

若是用 typedef,則,其規則是:

typedef 所表明的名稱 別名

例如,分別用 #define 和 typedef 來重命名 unsigned int 和 long long,示例以下:

#include <iostream>

#define uInt unsigned int

typedef long long ll;

int main() {
    uInt u = 102u;
    std::cout << u << std::endl;

    ll l = 999999999999;
    std::cout << l << std::endl;
    return 0;
}

輸出的結果是:

102
999999999999

C++11中,用using定義類型別名

若是用using來聲明類型別名,則,其規則是:

using 別名 = 所表明的名稱;

舉個例子:

#include <iostream>

using uInt = unsigned int;
using ll   = long long ;

int main() {
    uInt u = 102u;
    std::cout << u << std::endl;

    ll l = 999999999999;
    std::cout << l << std::endl;
    return 0;
}

其輸出的結果,和上面的一致,都是:

102
999999999999

Subsection-2.2:用using進行聲明

用using能夠用來聲明變量和函數,先看一個簡單程序:

#include <iostream>

int a = 10;

namespace ns {
    int a = 20;
}

void test() {
    int a = 30;
    std::cout << a << std::endl;
}

int main() {
    test();
    return 0;
}

其輸出結果是:

30

這是因爲就近原則,再也不解釋了~

而後,若是想要使用命名空間 ns 的變量 a 呢?能夠有這種作法:

#include <iostream>

int a = 10;

namespace ns {
    int a = 20;
}

void test() {
    int a = 30;
    using ns::a;
    std::cout << a << std::endl;
}

int main() {
    test();
    return 0;
}

可是這樣會帶來報錯:

在這裏插入圖片描述
緣由很重要:

using 進行聲明的時候,會產生二義性!意味着會和當前做用域其餘同名變量發送衝突!換句話說,using的聲明和普通的聲明,優先級一致!此時,編譯器就不知道你究竟是要訪問誰了?!

Subsection-2.2:用using編譯指令

聽起來很高級,其實就是爲編譯器補充了一個須要編譯的塊,這個塊能夠理解成一個盒子,盒子裏包裝這一個命名空間的所有內容!好比:

#include <iostream>

int a = 10;

namespace ns {
    int a = 20;
}

void test() {
    int a = 30;
    using namespace ns;
    std::cout << ns::a << std::endl;
}

int main() {
    test();
    return 0;
}

此時的輸出結果,是:

20

須要注意的是:

編譯了命名空間,只是讓編譯器對命名空間的內容完成編譯,而其優先級是 低於 當前做用域的聲明的!因而,仍是會進行就近原則!可是,咱們爲了可以輸出命名空間的變量,就和我們本文最開始講的那樣,顯式調用命名空間的變量!其中關鍵的程序是:

using namespace ns;
    std::cout << ns::a << std::endl;

Subsection-2.3:總結using的用法

  1. 在C++11裏,能夠用來給類型作別名
  2. 能夠進行聲明,可是要注意二義性,由於其和當前做用域的聲明級別同樣
  3. 能夠編譯指令,至關於告訴編譯器須要編譯命名空間的內容!級別低於當前做用域的聲明,若是須要調用命名空間,須要用做用域限定符!
相關文章
相關標籤/搜索