# typedef 与 using
# 对于typedef
和using
都能使用的场景
typedef
是C++98
中引入的给类型创建别名的方法
using
是C++11
中引入的给类型创建别名的方法,using
向下兼容typedef
所支持的操作,同时引入了新功能,比typedef
更强大,现代C++
推荐优先使用using
。
- 基本类型的别名
void foo()
{
using INT = int;
INT a = 12;
cout << "Testing: " << a << endl;
typedef double DOUBLE;
DOUBLE b = 1.2;
cout << "Testing: " << b << endl;
}
对于常规的类型,typedef
和using
的功能是相同的。
- 对函数的别名
void fooFunc(int a, int b)
{
cout << "Testing: " << a << " " << b << endl;
}
int funcAlias()
{
typedef void (*FP)(int, int);
FP fp = fooFunc;
fp(1, 2);
using FPU = void (*)(int, int);
FPU fpu = fooFunc;
fpu(3, 4);
}
如上代码中,声明了FP
和FPU
类型的函数指针,虽然两者功能相同,但从代码看上使用using
更加直观,一下子就能看出FPU
是某类型的别名。
# using
比typedef
强大的地方
- 模板别名
template <typename T>
class Array {
private:
T* ptr;
int size;
public:
Array(T arr[], int s);
~Array();
void print();
};
template <typename T>
Array<T>::Array(T arr[], int s)
{
ptr = new T[s];
size = s;
for (int i = 0; i < size; i++)
ptr[i] = arr[i];
}
template <typename T>
void Array<T>::print()
{
for (int i = 0; i < size; i++)
cout << " " << *(ptr + i);
cout << endl;
}
template <typename T>
Array<T>::~Array()
{
delete[] ptr;
}
template <typename T>
using Vec = Array<T>;
int main(int argc, char **argv)
{
int ar[5] = {10};
Vec<int> v(ar, 5);
v.print();
}
上面的代码中使用using
定义了模板类Array
的别名Vec
,假如使用typedef
来定义的话,代码在编译的时候就会报错如下,
/media/xx/data/code/basic_cplusplus_examples/test/src/utils.cpp:5:5: error: template declaration of ‘typedef’
5 | typedef Vec = Array<T>;
| ^~~~~~~
/media/xx/data/code/basic_cplusplus_examples/test/src/utils.cpp:5:13: error: ‘Vec’ does not name a type
5 | typedef Vec = Array<T>;
如果一定要用typedef
实现,可以写成这样,
template <typename T>
struct Vec
{
typedef Array<T> type;
};
// usage
Vec<int>::type vec;
不过,上面的代码就很丑了,而且还新定义了不必要的结构体,十分繁琐。
# auto
和decltype
auto
auto
类型说明符,能让编译器替我们去分析表达式所属的类型,其通过初始值来推算变量的类型。因此auto
定义的变量必须有初始值。auto
会忽略掉顶层的const
,因此希望推断出的的auto
类型是一个顶层const
时,需要明确的指出。
const int i = 100; auto j = i; // const auto j = i; j = 1000; cout << "j: " << j << endl;
上面的代码在编译运行的时候都不会报错,说明
auto
推理出的j
的类型是不带const
的。decltype
,这个关键字的引人是为了满足这种情况,当希望从表达式的类型推断出要定义的变量类型,但并不想用表达式的值初始化变量的时候,会使用decltype
。auto f = [](int a, int b){ return a + b; }; decltype(f(2, 3)) v = f(2, 3);
像这种使用方式,真觉得很魔幻。
C++
语法实在是冗杂。
# using
在面向对象编程中的应用
看下面这种情况,先定义了基类,又定义了派生类。
class Base {
public:
Base(int i) {}
Base(int a, int b) { }
Base(int a, int b, int c) { }
};
class Derived : public Base {
public:
Derived(int i) : Base(i) {}
Derived(int a, int b) : Base(a, b) {}
Derived(int a, int b, int c) : Base(a, b, c) {}
virtual void newMethod()
{
cout << "Testing: " << "New added Method.\n";
}
};
因为使用派生类创建对象时,会先调用基类的构造函数再调用派生类的构造函数。
在对象销毁时先调用派生类的析构函数,再调用基类的析构函数。
如上面的代码中所展示,派生类中只是增加了一个newMethod
方法,就需要对基类中的构造方法进行“补齐”,十分麻烦。
有没有什么简单的方法呢?using
可以用来应对这种情况。
class Base {
public:
Base(int i)
{
cout << "Calling Base Constructor.\n";
}
Base(int a, int b) { }
Base(int a, int b, int c) { }
virtual void printSelf()
{
cout << "Calling Base self print method.\n";
}
};
class Derived : public Base {
public:
using Base::Base;
using Base::printSelf;
virtual void newMethod()
{
cout << "Calling Derived newMethod.\n";
}
};
int main(int argc, char **argv)
{
auto ptr = std::make_shared<Derived>(10);
ptr->newMethod();
ptr->printSelf();
}
// Calling Base Constructor.
// Testing: New added Method.
// Base: print self method.
从上面的例子可以看到,使用using
声明后,就可以在派生类中使用基类的函数了。
值得注意的情况:
当使用多重继承时,使用using
声明有可能导致歧义,在这种情况下就要小心使用using
了。
class BaseA {
public:
BaseA(int i)
{
cout << "Calling BaseA Constructor.\n";
}
virtual void printSelf()
{
cout << "Print BaseA.\n";
}
};
class BaseB {
public:
BaseB(int i)
{
cout << "Calling BaseB Constructor.\n";
}
virtual void printSelf()
{
cout << "Print BaseB.\n";
}
};
class Derived : public BaseA, public BaseB {
public:
using BaseA::BaseA;
using BaseA::printSelf;
using BaseB::BaseB;
using BaseB::printSelf;
virtual void newMethod()
{
cout << "Print newMethos.\n";
}
};
上面代码编译将会报错,
/usr/include/c++/9/ext/new_allocator.h:146:4: error: call of overloaded ‘Derived(int)’ is ambiguous
使用
using BaseA::BaseA;
using BaseA::printSelf;
using BaseB::BaseB;
using BaseB::printSelf;
将给程序带来两个歧义,一个是Derived
类带一个整型变量的构造函数将不知道应该从BaseA
还是BaseB
中来继承;另外一个歧义来自printSelf
,同样的原因。
要想消除歧义,必须显式的定义有歧义的函数,如下:
class BaseA {
public:
BaseA(int i)
{
cout << "Calling BaseA.\n";
}
virtual void printSelf()
{
cout << "Calling BaseA printSelf.\n";
}
};
class BaseB {
public:
BaseB(int i)
{
cout << "Calling BaseB.\n";
}
virtual void printSelf()
{
cout << "Calling BaseB printSelf.\n";
}
};
class Derived : public BaseA, public BaseB {
public:
using BaseA::printSelf;
using BaseB::printSelf;
Derived() = default;
Derived(int i)
{
cout << "Testing: " << "New added Method.\n";
}
void printSelf()
{
BaseA::printSelf();
BaseB::printSelf();
}
virtual void newMethod();
};
如上,就可以整场编译运行了。