Bootstrap

C++中的类型别名与using声明

 

  1. 类型别名(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更灵活。
  1. using声明的其他用途
    • 命名空间(Namespace)相关的using声明
      • 当使用多个命名空间中的同名函数或者类型时,为了避免冗长的限定名称,可以使用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::MyClassusing 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类的一个公有成员一样。不过访问权限还是受到基类定义的限制,这里只是改变了它在派生类中的可访问性。

 

 

  1. 类型别名的使用方法
    • 基本类型别名
      • 对于简单的基本数据类型别名,比如定义一个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定义:

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的简单版本)。
      • 首先定义一个模板类:

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;
}

  1. using声明的使用方法(除类型别名外)
    • 命名空间的using声明
      • 单个名称引入:如果只想在当前作用域引入命名空间中的一个特定名称。例如,在std命名空间中有vector类型,你可以这样做:

之后在当前作用域就可以直接使用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类外部就可以访问这两个函数了。

;