目录
知识点一、namespace(名字空间/命名空间)
1、什么是namespace(名字空间/命名空间)
在C++中经常使用多个独立开发的库来完成项目,由于库的作者或开发人员没见过面,因此命名冲突在所难免。
2、为什么需要namespace(名字空间/命名空间)
在项目中函数名、全局变量、结构、联合、枚举、类,非常有可能名字冲突,而名字空间就对这些命名进行
逻辑空间划分(不是物理单元划分),为了解决命名冲突,C++之父为防止命名冲突给C++设计一个名字空间的机制。
通过使用namespace XXX把库中的变量、函数、类型、结构等包含在名字空间中,形成自己的作用域,避免
名字冲突。
namespace xxx
{
}// 没有分号
注意:namespace(名字空间/命名空间)也是一种标识符,在同一作用域下不能重名。
3、同名的namespace(名字空间/命名空间)有自动合并(为了声明和定义可以分开写)
同名的namespace(名字空间/命名空间)中如果有重名的依然会命名冲突
4、namespace(名字空间/命名空间)的使用方法
::域限定符
空间名::标识符 // 使用麻烦,但是非常安全
using namespace 空间名; 把空间中定义的标识符导入到当前代码中
不建议这样使用,相当于把垃圾分类后,又导入同一个垃圾车,依然会冲突
5、无名名字空间
不属于任何名字空间中的标识符,隶属于无名名字空间。
无名名字空间中的成员使用 ::标识符 进行访问。
如何访问被屏蔽的全局变量。
6、namespace(名字空间/命名空间)的嵌套
namespace(名字空间/命名空间)内部可以再定义名字空间,这种名字空间嵌套
内层的名字空间与外层的名字空间的成员,可以重名,内层会屏蔽外层的同名标识符。
多层的名字空间在使用时逐层分解。
7、可以给namespace(名字空间/命名空间)取别名
由于namespace(名字空间/命名空间)可以嵌套,这样就会导致在使用内层成员时过于麻烦,可以给
namespace(名字空间/命名空间)取别名来解决这类问题。
#include <iostream>
namespace test
{
int cout = 100;
int cin = 99;
}
int main()
{
std::cout << test::cout << ' ' << test::cin << std::endl;
}
//输出:
//[Running] cd "f:\VS Code Demo\" && g++ 1.cpp -o 1 && "f:\VS Code Demo\"1
//100 99
#include <iostream>
// n1::n2::num;
namespace n1
{
int num = 1;
namespace n2
{
int num = 2;
namespace n3
{
int num = 3;
}
}
}
int main()
{
std::cout << n1::num << ' ' << n1::n2::num << ' ' << n1::n2::n3::num << std::endl;
}
//7、可以给namespace(名字空间/命名空间)取别名
// 由于namespace(名字空间/命名空间)可以嵌套,这样就会导致在使用内层成员时过于麻烦,可以给
//namespace(名字空间/命名空间)取别名来解决这类问题。
namespace n123 = n1::n2::n3;
std::cout << n123::num << std::endl;
知识点二、重载和重写的区别
重载:在同一个类中定义多个方法名相同但参数列表不同的方法,如下所示:
#include <iostream>
using namespace std;
class Calculator {
public:
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
};
int main() {
Calculator calc;
cout << calc.add(1, 2) << endl; // 调用第一个add方法
cout << calc.add(1.2, 3.4) << endl; // 调用第二个add方法
cout << calc.add(1, 2, 3) << endl; // 调用第三个add方法
return 0;
}
重写:子类可以对父类中的方法进行覆盖,如下所示:
#include <iostream>
using namespace std;
class Animal {
public:
virtual void move() {
cout << "Animal is moving" << endl;
}
};
class Dog : public Animal {
public:
virtual void move() {
cout << "Dog is running" << endl;
}
};
int main() {
Animal *animal = new Dog();
animal->move(); // 调用Dog类中重写的move方法
delete animal;
return 0;
}
知识点三、四种强制类型转换
static_cast<type>(expression)
该运算符把 expression 转换为 type 类型,主要用于基本数据类型之间的转换,如把 uint 转换为 int,把 int 转换为 double 等。
另外,static_cast 还可用于类层次结构中,基类和派生类之间指针或引用的转换,但也要注意:
static_cast 进行上行转换是安全的,即把派生类的指针转换为基类的;
static_cast 进行下行转换是不安全的,即把基类的指针转换为派生类的。
注:static_cast 没有运行时类型检查来保证转换的安全性,需要程序员来判断转换是否安全。
//1、将整型数据转换为浮点型数据
int a = 10;
float b = static_cast<float>(a);
//2、将指针类型转换为另一个指针类型
class Base {};
class Derived : public Base {};
Base* b = new Base();
Derived* d = static_cast<Derived*>(b);
//将指针类型转换为整型数据
int* p = new int(10);
intptr_t n = static_cast<intptr_t>(p);
//将枚举类型转换为整型数据
enum class Color { RED, GREEN, BLUE };
int n = static_cast<int>(Color::GREEN);
const_cast<type>(expression)
常量指针被转化成非常量指针,并且仍然指向原来的对象;
常量引用被转换成非常量引用,并且仍然指向原来的对象;
常量对象被转换成非常量对象。
#include <iostream>
using std::cout;
using std::endl;
int main(void)
{
const int b = 10;
//b = 11 报错,因为b是一个常量对象
int * pc = const_cast<int *>(&b);
*pc = 11;
cout << "*pc = " << *pc << endl;//b原来地址的数据现在可由*pc来改变,即解除const
cout << "b = " << b << endl; //b其实类似(编译器处理问题)#define b 10 不会变的
return 0;
}
dynamic_cast<type>(expression)
主要用于类层次间的上行转换或下行转换。在进行上行转换时,dynamic_cast 和 static_cast 的效果是一样的,但在下行转换时,dynamic_cast 具有类型检查的功能,比 static_cast 更安全
// 1、将基类指针或引用转换成派生类指针或引用
//基类指针转换成派生类指针
Base* base_ptr = new Derived();
Derived* derived_ptr = dynamic_cast<Derived*>(base_ptr);
// 基类引用转换成派生类引用
Base& base_ref = *base_ptr;
Derived& derived_ref = dynamic_cast<Derived&>(base_ref);
//2、将派生类指针或引用转换成基类指针或引用
// 派生类指针转换成基类指针
Derived* derived_ptr = new Derived();
Base* base_ptr = dynamic_cast<Base*>(derived_ptr);
// 派生类引用转换成基类引用
Derived& derived_ref = *derived_ptr;
Base& base_ref = dynamic_cast<Base&>(derived_ref);
//3、将指向虚基类子对象的指针或引用转换成指向最终派生类对象的指针或引用:
class Base {
public:
virtual ~Base() {}
};
class Derived1 : public virtual Base {};
class Derived2 : public virtual Base {};
class Final : public Derived1, public Derived2 {};
// 指向虚基类子对象的指针转换成指向最终派生类对象的指针
Derived1* d1_ptr = new Final();
Base* base_ptr = dynamic_cast<Base*>(d1_ptr);
Final* final_ptr = dynamic_cast<Final*>(base_ptr);
// 指向虚基类子对象的引用转换成指向最终派生类对象的引用
Derived1& d1_ref = *d1_ptr;
Base& base_ref = dynamic_cast<Base&>(d1_ref);
Final& final_ref = dynamic_cast<Final&>(base_ref);
reinterpret_cast<type>(expression)
将指针或引用转换为其他类型的指针或引用。例如,将 void* 指针转换为另一种指针类型。
将整数类型转换为指针类型。例如,将 int 转换为 void* 指针。
将指针类型转换为整数类型。例如,将 void* 指针转换为 int。
//1、将指针或引用转换为其他类型的指针或引用:
int i = 10;
void* p = &i;
int* pi = reinterpret_cast<int*>(p);
//2、将整数类型转换为指针类型:
int i = 10;
void* p = reinterpret_cast<void*>(i);
//3、将指针类型转换为整数类型
int i = 10;
void* p = &i;
int pi = reinterpret_cast<int>(p);
知识点四、const
修饰变量,说明该变量不可以被改变;
修饰指针,分为指向常量的指针和指针常量;
常量引用,经常用于形参类型,即避免了拷贝,又避免了函数对值的修改;
修饰成员函数,说明该成员函数内不能修改成员变量
// 类
class A
{
private:
const int a; // 常对象成员,只能在初始化列表赋值
public:
// 构造函数
A() { };
A(int x) : a(x) { }; // 初始化列表
// const可用于对重载函数的区分
int getValue(); // 普通成员函数
int getValue() const; // 常成员函数,不得修改类中的任何数据成员的值
};
void function()
{
// 对象
A b; // 普通对象,可以调用全部成员函数
const A a; // 常对象,只能调用常成员函数、更新常成员变量
const A *p = &a; // 常指针
const A &q = a; // 常引用
// 指针
char greeting[] = "Hello";
char* p1 = greeting; // 指针变量,指向字符数组变量
const char* p2 = greeting; // 指针变量,指向字符数组常量
char* const p3 = greeting; // 常指针,指向字符数组变量
const char* const p4 = greeting; // 常指针,指向字符数组常量
}
// 函数
void function1(const int Var); // 传递过来的参数在函数内不可变
void function2(const char* Var); // 参数指针所指内容为常量
void function3(char* const Var); // 参数指针为常指针
void function4(const int& Var); // 引用参数在函数内为常量
// 函数返回值
const int function5(); // 返回一个常数
const int* function6(); // 返回一个指向常量的指针变量,使用:const int *p = function6();
int* const function7(); // 返回一个指向变量的常指针,使用:int* const p = function7();
知识点五、inline 内联函数
相当于把内联函数里面的内容写在调用内联函数处;
相当于不用执行进入函数的步骤,直接执行函数体;
相当于宏,却比宏多了类型检查,真正具有函数特性;
不能包含循环、递归、switch 等复杂操作;
在类声明中定义的函数,除了虚函数的其他函数都会自动隐式地当成内联函数。
// 声明1(加 inline,建议使用)
inline int functionName(int first, int secend,...);
// 声明2(不加 inline)
int functionName(int first, int secend,...);
// 定义
inline int functionName(int first, int secend,...) {/****/};
// 类内定义,隐式内联
class A {
int doA() { return 0; } // 隐式内联
}
// 类外定义,需要显式内联
class A {
int doA();
}
inline int A::doA() { return 0; } // 需要显式内联
知识点六、初始化列表
初始化列表转换是一种C++中的类型转换,它允许使用初始化列表来显式地将一个对象转换为另一个类型。初始化列表转换通常用于将一个类的对象转换为另一个类的对象。
首先不使用初始化列表:
//下面是不使用初始化列表的代码示例:
class Person {
public:
Person(const std::string& name, int age) {
name_ = name;
age_ = age;
}
std::string name() const { return name_; }
int age() const { return age_; }
private:
std::string name_;
int age_;
};
class Employee {
public:
Employee(const std::string& name, int age, int salary) {
person_ = Person(name, age);
salary_ = salary;
}
std::string name() const { return person_.name(); }
int age() const { return person_.age(); }
int salary() const { return salary_; }
private:
Person person_;
int salary_;
};
int main() {
Employee employee("John", 30, 5000);
std::cout << "Name: " << employee.name() << std::endl;
std::cout << "Age: " << employee.age() << std::endl;
std::cout << "Salary: " << employee.salary() << std::endl;
return 0;
}
使用初始化列表表示
//初始化列表代码
class Person {
public:
Person(const std::string& name, int age) {
name_ = name;
age_ = age;
}
std::string name() const { return name_; }
int age() const { return age_; }
private:
std::string name_;
int age_;
};
class Employee {
public:
Employee(const std::string& name, int age, int salary) {
person_ = Person(name, age);
salary_ = salary;
}
std::string name() const { return person_.name(); }
int age() const { return person_.age(); }
int salary() const { return salary_; }
private:
Person person_;
int salary_;
};
int main() {
Employee employee("John", 30, 5000);
std::cout << "Name: " << employee.name() << std::endl;
std::cout << "Age: " << employee.age() << std::endl;
std::cout << "Salary: " << employee.salary() << std::endl;
return 0;
}