目录
命名空间:
假设在一个头文件LibA中定义了一个全局变量log
然后在另一个头文件LibB中也定义了一个全局变量log
当我们包含这两个头文件并且开始使用这个全局变量时,编译就会出错:
cpp
// LibA 的定义(可能是某个头文件)
int log = 1; // LibA 的日志级别
// LibB 的定义(可能是另一个头文件)
int log = 2; // LibB 的日志级别
int main() {
std::cout << log; // 编译错误:'log' 重复定义
return 0;
}
这个时候我们使用命名空间,在LibA文件中定义一个命名空间LibA,在LibB中定义一个命名空间LibB,并且在命名空间中来定义这个全局变量,这时我们就可以在主函数中指定命名空间来访问不同文件中的全局变量log。
cpp
// LibA 的命名空间
namespace LibA {
int log = 1; // LibA::log
}
// LibB 的命名空间
namespace LibB {
int log = 2; // LibB::log
}
int main() {
std::cout << LibA::log << "\n"; // 输出 1
std::cout << LibB::log << "\n"; // 输出 2
return 0;
}
此时命名空间就解决了全局变量命名冲突的问题。
命名空间的使用:
假设一个命名空间Utils:
cpp
namespace Utils {
int version = 3;
void print() { std::cout << "Utils version: " << version << "\n"; }
void log(const std::string& msg) { std::cout << "[LOG] " << msg << "\n"; }
}
1、指定命名空间访问:
空间名::成员变量/函数名。
cpp
int main() {
std::cout << Utils::version << "\n"; // 输出 3
Utils::print(); // 调用 Utils::print()
Utils::log("Hello"); // 调用 Utils::log()
return 0;
}
2、展开某个成员变量或者函数:
使用using关键字,指定哪个成员变量/函数,就展开哪个。
cpp
int main() {
using Utils::version; // 只引入 version
using Utils::log; // 只引入 log
std::cout << version << "\n"; // 直接使用 version
log("Hello"); // 直接调用 log()
// print(); // 错误!未引入 print()
return 0;
}
3、展开整个命名空间:
使用using关键字,指定哪个命名空间就展开哪个。
cpp
int main() {
using namespace Utils; // 引入整个 Utils
std::cout << version << "\n"; // 直接使用 version
print(); // 直接调用 print()
log("Hello"); // 直接调用 log()
return 0;
}
展开命名空间,实际上是编译器在查找变量、函数、类型、的时候,不仅会和原来一样,从局部域中找,从全局域中找,还会额外从展开了的命名空间去找,如果此时展开的命名空间中定义的变量名和某个全局变量产生了冲突,编译器还是会报错。
所以展开整个命名空间的操作是有风险的,只不过为了方便,写小练习的时候会使用。
缺省参数:
全缺省:
cpp
#include <iostream>
using namespace std;
// 全缺省函数:所有参数都有默认值
void printInfo(string name = "Unknown", int age = 0, string country = "Earth") {
cout << "Name: " << name << ", Age: " << age << ", Country: " << country << endl;
}
int main() {
printInfo(); // 输出: Name: Unknown, Age: 0, Country: Earth
printInfo("Alice"); // 输出: Name: Alice, Age: 0, Country: Earth
printInfo("Bob", 25); // 输出: Name: Bob, Age: 25, Country: Earth
printInfo("Charlie", 30, "Mars");// 输出: Name: Charlie, Age: 30, Country: Mars
return 0;
}
半缺省:
半缺省参数,必须从右到左连续的给缺省值。
cpp
#include <iostream>
using namespace std;
// 半缺省函数:部分参数有默认值(必须从右往左连续缺省)
void connectDatabase(string ip, int port = 3306, string user = "root") {
cout << "Connecting to: " << ip << ":" << port << " as " << user << endl;
}
int main() {
// connectDatabase(); // 错误!ip 无默认值,必须传参
connectDatabase("127.0.0.1"); // 输出: Connecting to: 127.0.0.1:3306 as root
connectDatabase("192.168.1.1", 5432); // 输出: Connecting to: 192.168.1.1:5432 as root
connectDatabase("10.0.0.1", 8080, "admin"); // 输出: Connecting to: 10.0.0.1:8080 as admin
return 0;
}
1、调用有缺省值的函数时,必须从左到右连续的给实参
cpp
void connect(string ip, int port = 3306, string user = "root") {
cout << "IP: " << ip << ", Port: " << port << ", User: " << user << endl;
}
//正确调用方式
connect("127.0.0.1"); // 只传 ip,port 和 user 用默认值
connect("192.168.1.1", 5432); // 传 ip 和 port,user 用默认值
connect("10.0.0.1", 8080, "admin");// 传所有参数
//错误调用方式
connect(); // 错误!ip 必须传参
connect("localhost", , "guest"); // 错误!不能跳过 port 直接传 user
2、缺省参数不能在函数的声明和定义中同时出现,这是为了避免声明和定义中缺省值不同的情况。
cpp
// 声明(通常在头文件 .h 中)
void printInfo(string name = "Unknown", int age = 0);
// 定义(在 .cpp 文件中)
void printInfo(string name, int age) {
cout << "Name: " << name << ", Age: " << age << endl;
}
printInfo(); // 输出: Name: Unknown, Age: 0
printInfo("Alice"); // 输出: Name: Alice, Age: 0
printInfo("Bob", 25); // 输出: Name: Bob, Age: 25
函数重载:
c++允许同一作用域中出现同名函数,只不过这些同名函数 的参数不同,这样才能在调用函数时区分调用的是哪个函数。
引用:
引用就是给对象起一个别名,使用方法是:类型 & 别名。
引用必须初始化:
cpp
int main() {
int x = 10;
int &ref; // ❌ 错误!引用必须初始化
int &ref = x; // ✅ 正确:ref 是 x 的别名
return 0;
}
引用定义后就无法改变指向对象:
cpp
int main() {
int x = 10;
int y = 20;
int &ref = x; // ref 绑定到 x
ref = y; // ❌ 不是改变绑定,而是把 y 的值赋给 x!
std::cout << x; // 输出 20(x 的值被修改)
std::cout << ref; // 输出 20(ref 仍然是 x 的别名)
return 0;
}
由于引用必须初始化,所以不存在指向空值的引用。
下面行为是未定义的:
cpp
//空引用
int* ptr = NULL;
int& rb = *ptr;
rb++;
由于引用底层使用的是指针,所以"引用传值,指针传地址"这句话是错的。
指针和引用的关系:
1、从概念上讲,引用就是给一个变量取一个别名,不会新开一个空间,而指针则是取出一个变量的地址。
2、引用在定义时必须初始化,而指针不必须
3、引用无法改变引用的对象,而指针可以改变指向的对象,这也是引用无法替代指针的点。
4、引用可以直接访问指向的对象,而指针需要先解引用。
5、sizeof(别名)、sizeof(指针)的值不同,引用的sizeof代表指向对象的大小,而指针永远表示地址空间所占字节数。
6、由于引用必须初始化,所以使用起来比指针安全一些。
inline:
当一个函数的代码量很少,但是调用的却很频繁时,为了减少函数栈帧创建带来的时间开销,采取空间换时间的方式,直接在调用的地方展开函数,而不是为它创建栈帧。
cpp
// 在声明和定义处都加 inline(推荐)
inline int add(int a, int b) {
return a + b;
}
// 调用时和普通函数一样
int result = add(3, 5); // 可能被展开为:int result = 3 + 5;
inline对于编译器而言只是一个建议,当函数代码量过大或者是递归层次过深,这个建议就会被忽略。
C语言中的宏函数也是为了解决小函数调用栈帧开销过大的问题,但是实现起来过于繁琐和复杂。
inline的声明和定义不能放在不同文件中:
cpp
// 错误示例:分离声明和定义
// math.h
inline int add(int a, int b); // 只有声明
// math.cpp
inline int add(int a, int b) { return a + b; } // 定义在另一个文件
// main.cpp
#include "math.h"
int main() {
int r = add(2, 3); // 链接错误:找不到定义
return 0;
}
建议定义也放在头文件中:
cpp
// math.h
inline int add(int a, int b) { // 声明和定义在一起
return a + b;
}
// main.cpp
#include "math.h"
int main() {
int r = add(2, 3); // 正确:编译器看到定义后可以内联
return 0;
}