c++-base
C++基础语法
1. namespace命名空间
命名空间是C++中用于划分代码,防止命名冲突的重要机制。它可以将全局作用域划分为不同的命名区域,让相同名称的标识符在不同命名空间中互不干扰。C++中域分为函数局部域、全局域、命名空间域、类域。
1.1 命名空间的定义
命名空间中可以定义变量、函数、类等。
namespace name {int value = 0;void func() {}class Obj {};
}
1.2 命名空间的访问
编译查找一个变量时的声明/定义时,默认只会在局部或者全局查找,不会到命名空间里去查找,所以要访问命名空间中的变量/函数,有三种方式。
1.2.1 域作用限定符
访问命名空间,使用域作用修饰限定符 ::
。
name::value = 10;
name::func();
name::Obj obj;
1.2.2 using声明引入特定成员
using name::value;
value = 10;
1.2.3 using引入整个命名空间
using namespace name;
value = 10;
func();
Obj obj;
项目中不推荐这种方式。
1.3 全局变量的访问
当局部变量和全局变量冲突时,根据就近原则默认访问的是局部变量,这时我们可以通过::
来访问全局变量。
#include <iostrea>
using namespace std;
int a = 100;
int main () {int a = 10;cout << a << endl; // 10cout << ::a << endl; // 100
}
1.4 命名空间的特性
1.4.1 命名空间的嵌套
namespace Outer {int x = 10;namespace Inner {int y = 20;}
}// 访问方式
Outer::x;
Outer::Inner::y;
1.4.2 命名空间可以分段定义
多文件中可以定义同名的namespace,他们会默认合并到一起。
// 文件1.cpp
namespace MyNS {void func1();
}// 文件2.cpp
namespace MyNS {void func2();
}
1.4.3 命名空间别名
namespace name {int x = 10;
}
namespace newName = name; // 别名
newName::x;
name::x;
C++标准库都放在std的命名空间中。
2. C++输入输出
<iostream>是Input Output Stream的缩写,是标准输入、输出流库,定义了标准输入输出对象。
cin、cout可以自动识别类型。
2.1 标准输入
std::cin是istream类的对象,它是标准输入流。
#include <iostream>
int main () {int a = 0;float f = 0;std::cin >> a >> f;
}
>>流提取运算符,本质是运算符重载。
2.2 标准输出
std::cout是ostream类的对象,它是标准输出流。
#include <iostream>
int main () {int a = 10;float f = 22.22;std::cout << a << f;
}
<<流插入运算符,本质是运算符重载。
2.3 提升输入输出效率
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
io需求比较高时,添加这三行代码可以提升C++IO效率。
3. 缺省参数
缺省参数是定义或声明函数时为函数参数指定一个缺省值,调用该函数时,若没有传参则使用缺省值,否则使用指定形参。
带缺省参数的函数调用,C++规定必须从左到右依次传参,不能跳跃
3.1 全缺省
全缺省是给所有形参一个缺省值。
void func(int a = 10 , int b = 20 , int c = 30) {}
3.2 半缺省
半缺省是给部分形参缺省值。C++规定半缺省参数必须从右往左依次连续缺省,不能跳跃。
void func(int a , int b = 10, int c = 20) {}
函数定义和声明分离时,缺省参数不能在函数定义和声明中同时出现,C++规定必须在函数声明处给缺省值。
4. 函数重载
C++支持在同一作用域中出现同名函数,但是这些函数的参数列表必须不同。
4.1 函数重载基本规则
-
函数名必须相同
-
参数列表必须不同
-
参数类型不同
-
参数个数不同
-
参数顺序不同(仅当类型不同时有效)
-
-
返回值不同并不能区分重载
4.2 C++支持函数重载原理
为什么C++支持函数重载,而C不支持呢?以Linux环境为例。
首先函数的声明只是为了编译通过,而具体的实现是编译器在链接阶段去找函数的地址,那么在链接时,假设面对Add函数,链接器会使用什么函数名去找呢?其实每个编译器都有一套自己的函数名修饰规则。
函数名修饰规则:编译器在编译时会将函数名和参数信息结合,生成一个全局唯一的内部名称,以此区分函数重载。
C语言中函数名修饰规则:直接使用定义/声明中的函数名,在链接时,编译器会直接使用函数名寻找函数地址。
C++中函数名修饰规则:_Z + 函数名长度 + 函数名 + 类型首字母。
int add(int a, int b){} // 修饰后的函数名: _Z3addii
上述可以深刻的理解为什么C语言中不支持函数重载?因为函数名修饰规则会导致寻找函数地址时发生冲突;为什么函数重载并不用返回值来区分?因为函数名修饰规则。
5. 引用
引用在语法上是为一个已经存在的变量起别名,可以理解为并不占用空间。
5.1 引用的特性
-
引用在定义时必须初始化
-
一个变量可以有多个引用
-
引用一旦引用一个实体,就不能改变指向了
#include <iostream>
using namespace std; int main () { int x = 0; int& b = x; // b为x的别名 修改b相当于修改x 使用b相当于使用 int& c = x; // 一个变量可以有多个引用int num = 10;b = num; // 引用一旦引用实体,则不能改变指向,这行代码的意思是把num值赋值给b也就是x return 0;
}
5.2 const引用
const引用既可以引用普通对象,也可以引用一个const对象,但是在C++中,const引用有时候是必不可少的。
5.2.1 const引用权限放大缩小问题
引用的权限可以缩小,但是不能放大。
/** int a是一个普通变量可以读可以写* const int& 表示的是该引用类型的别名 只能读不能修改* 相当于对于引用是权限的缩小(√)
*/
int a = 10;
const int& ref1 = a;/** const int a是一个常量可以读,但是不能写* int& 表示的是该引用类型的别名,可以读可以修改* 我本身的变量都不能修改,但是却要赋值给一个可以修改引用别名上,这是权限的放大(❌)
*/
const int b = 10;
int& ref2 = b;
注意:只有引用和指针才有权限放大缩小问题。
5.2.2 const引用使用场景
// 1.const引用常量
const int& ref1 = 10;
// 2.const引用const变量
const int num = 10;
const int& ref2 = num;
// 3. const引用函数返回值
int test () {int x = 100;return x;
}
int& ref3 = test(); // ×
// 返回值x会产生一个临时的对象,把x的值赋值给这个临时的对象,而临时对象具有常性不能修改,所以要使用const引用
const int& ref3 = test(); // √
// 4. const引用表达式
int a = 1;
int b = 2;
const int& ref4 = a + b; // a+b这个表达式会产生一个临时的对象,而临时对象具有常性不能修改,只能使用const引用
// 5. const引用发生类型转换
float f = 1.1;
const int& ref5 = f; // float类型赋值给int类型,会产生一个临时的对象,把f变量截断后的整数赋值给临时对象,
产生临时对象/变量的场景:
函数返回值
一些表达式(a+b、a*3等)
类型转换
-
所谓临时对象/变量就是编译器需要一个空间暂存表达式的结果,临时创建的一个未命名的对象/变量(大部分情况是寄存器),而临时对象/变量是具有常性的不能修改。
-
临时对象/变量被const引用,会延长其生命周期。
5.3 指针和引用的区别
引用 | 指针 | |
---|---|---|
1 | 语法上引用代表变量的别名,不开辟空间 | 指针是存储一个变量的地址,需要开辟空间 |
2 | 引用定义时必须初始化 | 指针定义时可以不初始化 |
3 | 引用不可以改变指向 | 指针可以改变指向 |
4 | 引用可以直接访问对象 | 指针需要解引用才能访问对象 |
5 | sizeof(引用)表示是被引用对象的大小 | sizeof(指针)表示指针的大小(32位机器4字节、64位机器8字节) |
6 | 引用相较于指针更安全一些 | 指针会出现野指针等情况 |
6. inline
使用inline关键字修饰的函数称为内联函数,编译时C++编译器会在调用的地方展开内联函数,而无需建立栈帧提高效率,内联函数的出现主要是替代宏函数。
宏函数有许多坑,比如写一个Add宏函数。
#define Add(x , y) ((x)+(y))
-
为什么不能加分号?因为宏函数可能在if语句或输出语句中调用,带分号会编译报错
-
为什么要加外面的括号?因为有运算符优先级问题,比如
Add(1 , 2) * 5
没有外层括号会产生意想不到的结果 -
为什么要加里面的括号?因为有运算符优先级问题,比如
Add(1 | 2 , 3 ^ 5)
没有内层括号会产生意想不到的结果
inline的特性:
inline是一个建议性的选项,inline适合短小的函数,对于代码量大的函数或递归函数,编译器会忽略。
debug版本下面默认不展开inline函数,这样方便调试。
inline函数不能声明和定义分离,会导致链接错误。 因为inline函数默认是展开,没有函数地址,调用时也就找不到具体实现,无法完成展开。建议inline函数直接写在.h文件中。
7. nullptr
nullptr是C++11中引入的关键字,用于表示空指针常量。它解决了传统NULL宏在C++中一些问题,提供了更安全、更明确的空指针表示方式。
在C++中NULL通常被定义0,在C语言中NULL通常被定义为((void*)0),但是无论哪种方式,都会导致一些问题。
void test(int);
void test(char*);test(NULL); // 调用哪个?调用的是test(int),而不是预期的test(char*)
C++中推荐使用nullptr。