- 类型别名(Type Alias)的概念
- 类型别名是为一个已存在的数据类型提供一个新的名字。它可以让代码更易读,也方便在复杂的类型定义场景下使用更简洁的名称来表示复杂类型。
- 在 C++ 中有两种主要方式来定义类型别名:typedef和using。
- 使用typedef定义类型别名
- 例如,如果你有一个比较复杂的指针类型int *,你可以为它定义一个别名:
typedef int *IntPtr;
这里IntPtr就是int*的别名。之后你可以像使用int*一样使用IntPtr。例如:
IntPtr p = new int(5);
std::cout << *p << std::endl;
- using声明定义类型别名(在类型别名方面与typedef类似)
- 还是以int*为例,使用using可以这样定义类型别名:
using IntPtr = int*;
- 这和上面typedef的效果是一样的。using在 C++11 中引入这种语法来定义类型别名,它在模板别名等一些复杂场景下比typedef更灵活。
- using声明的其他用途
- 命名空间(Namespace)相关的using声明
- 当使用多个命名空间中的同名函数或者类型时,为了避免冗长的限定名称,可以使用using声明将命名空间中的名称引入当前作用域。例如:
- 命名空间(Namespace)相关的using声明
namespace NS1 {
class MyClass {
public:
void func() {
std::cout << "NS1::MyClass::func" << std::endl;
}
};
}
namespace NS2 {
class MyClass {
public:
void func() {
std::cout << "NS2::MyClass::func" << std::endl;
}
};
}
如果你想在主函数中使用NS1中的MyClass,可以这样做:
int main() {
using NS1::MyClass;
MyClass obj;
obj.func();
return 0;
}
- 这样就把NS1::MyClass引入到了main函数的作用域中,方便使用。不过要注意,如果同时使用using NS1::MyClass和using NS2::MyClass在同一个作用域,就会产生冲突,因为编译器不知道你具体要使用哪个MyClass。
- 继承中的using声明
- 在类继承中,using声明可以用来改变基类成员的访问权限或者引入基类的隐藏成员。例如:
class Base {
public:
void func() {
std::cout << "Base::func" << std::endl;
}
};
class Derived : private Base {
public:
using Base::func;
};
在这里,Base类是Derived类的私有基类,正常情况下Base中的func函数在Derived类外部是不可访问的。但是通过using Base::func声明,在Derived类外部就可以访问func函数了,就好像func是Derived类的一个公有成员一样。不过访问权限还是受到基类定义的限制,这里只是改变了它在派生类中的可访问性。
- 类型别名的使用方法
- 基本类型别名
- 对于简单的基本数据类型别名,比如定义一个uint作为unsigned int的别名。使用typedef可以这样定义:
- 基本类型别名
typedef unsigned int uint;
或者使用using(C++11 及以上):
using uint = unsigned int;
之后在代码中就可以用uint来声明变量,例如:
uint num = 10;
- 复杂类型别名(指针、数组等)
- 指针类型别名:当定义一个指向函数的指针类型别名时,假设你有一个函数int add(int a, int b),可以定义一个指向这种函数的指针类型别名。
- 用typedef定义:
- 指针类型别名:当定义一个指向函数的指针类型别名时,假设你有一个函数int add(int a, int b),可以定义一个指向这种函数的指针类型别名。
typedef int (*AddFuncPtr)(int, int);
- 用using定义:
using AddFuncPtr = int (*)(int, int);
- 然后可以使用这个别名来声明函数指针变量:
AddFuncPtr p = add;
int result = p(3, 5);
- 数组类型别名:定义一个包含 10 个int元素的数组类型别名。
- 用typedef定义:
typedef int IntArray[10];
- 用using定义:
using IntArray = int[10];
- 接着可以使用别名声明数组变量:
IntArray myArray;
for (int i = 0; i < 10; ++i) {
myArray[i]=i;
}
- 模板类型别名(C++11 及以上)
- 考虑一个简单的模板类型别名,用于定义一个可以存储不同类型元素的动态数组(类似于std::vector的简单版本)。
- 首先定义一个模板类:
- 考虑一个简单的模板类型别名,用于定义一个可以存储不同类型元素的动态数组(类似于std::vector的简单版本)。
template <typename T>
class MyArray {
private:
T* data;
size_t size;
public:
MyArray(size_t n) : size(n) {
data = new T[n];
}
~MyArray() {
delete[] data;
}
T& operator[](size_t i) {
return data[i];
}
};
- 然后使用using定义模板类型别名(typedef在这种情况下很难用于定义模板类型别名):
using IntDynamicArray = MyArray<int>;
using DoubleDynamicArray = MyArray<double>;
- 可以像下面这样使用这些别名:
IntDynamicArray intArray(5);
for (size_t i = 0; i < 5; ++i) {
intArray[i] = i;
}
DoubleDynamicArray doubleArray(3);
for (size_t i = 0; i < 3; ++i) {
doubleArray[i] = i * 1.0;
}
- using声明的使用方法(除类型别名外)
- 命名空间的using声明
- 单个名称引入:如果只想在当前作用域引入命名空间中的一个特定名称。例如,在std命名空间中有vector类型,你可以这样做:
- 命名空间的using声明
之后在当前作用域就可以直接使用vector来声明变量,如vector<int> myVec;,而不需要写成std::vector<int> myVec;。
- 整个命名空间引入(不推荐在头文件中使用):如果想在当前作用域引入整个命名空间,可以使用using namespace语句。但这种方式可能会导致命名冲突,所以一般只在小的局部作用域(如函数内部)使用。例如:
void myFunction() {
using namespace std;
string str = "Hello";
cout << str << endl;
}
- 继承中的using声明
- 用于引入基类的构造函数。假设基类Base有多个构造函数,派生类Derived可以使用using声明来继承这些构造函数。
class Base {
public:
Base(int a) {}
Base(double d) {}
};
class Derived : public Base {
public:
using Base::Base;
};
这样,Derived类就继承了Base类的所有构造函数,可以像下面这样使用:
Derived obj1(5); // Calls Base(int a) constructor
Derived obj2(3.14); // Calls Base(double d) constructor
- 用于改变基类成员函数的访问权限(如前面提到的将私有基类中的成员函数在派生类中变为公有可访问)。例如:
class Base {
public:
void publicFunc() {}
protected:
void protectedFunc() {}
};
class Derived : protected Base {
public:
using Base::publicFunc;
using Base::protectedFunc;
};
在这个例子中,Derived类通过using声明将基类Base中的protectedFunc函数在Derived类中的访问权限变为公有,同时也保留了publicFunc的公有访问权限。这样在Derived类外部就可以访问这两个函数了。