C++基础知识01
Section01:双冒号运算符与作用域
Subsection-1.1:作用域
在C/C++的作用域中,讲究一个就近原则。也就是说,在局部和全局都对某个变量进行了定义时,我们的访问/调用会去操作 “最近” 的那个变量!例如下面的例子:
#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;
}
然后再看一下运行效果:
 这就是双冒号运算符,改变作用域的效果。在没有指定命名空间的时候,作用域就是全局作用域!
Subsection-1.2:双冒号运算符使用命名空间
所谓命名空间,是管理命名列表而生的。之所以需要对命名进行管理,那是为了在 “迭代开发” 或者是多人 “合作开发” 中,避免 “命名冲突” 的危害!
如何调用命名空间的变量和函数呢?那就是用双冒号作用域限定符!看一个需求:假设在游戏开发中,先开发了英雄露娜的操作,例如:攻击、大招;随后又开发了英雄澜的操作,其中也有攻击和大招,请问怎么实现比较好呢?给出示例程序:
(1)先看看开发Lunar的过程
头文件(实现定义,注意定义就要在命名空间里定义):
#pragma once
#ifndef __LUNAR__
#define __LUNAR__
#include <iostream>
using namespace std;
namespace lunar {
void goAtk();
void goUltimate();
}
#endif
CPP文件(负责实现):
#include "Lunar.h"
void lunar::goAtk() {
cout << "Lunar 释放攻击!" << endl;
}
void lunar::goUltimate() {
cout << "Lunar 释放大招!" << endl;
}
(2)随后开发Lan的过程
头文件:
#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的用法
- 在C++11里,可以用来给类型做别名
- 可以进行声明,但是要注意二义性,因为其和当前作用域的声明级别一样
- 可以编译指令,相当于告诉编译器需要编译命名空间的内容!级别低于当前作用域的声明,如果需要调用命名空间,需要用作用域限定符!
|