静态类型和动态类型的区别
- 静态类型的类型检查主要发生在编译阶段
- 动态类型的类型检查主要发生在运行阶段
关键字auto
传统auto是一个存储类型指示符(storage-class-specifier),使用auto修饰的变量是具有自动存储期的局部变量,而C++11中auto是作为一个新的类型指示符(type-specifier),它声明的变量的类型必须由编译器在编译时期推导而得。
int main()
{
double foo();
auto x = 1; // x的类型为int
auto y = foo(); // y的类型为double
struct m { int i; }str;
auto str1 = str; // str1的类型是struct m
auto z; // 无法推导,无法通过编译
z = x;
}
值得注意的是变量z,这里我们使用auto关键字来“声明”z,但不立即对其进行定义,此时编译器则会报错。这跟通过其他关键字先声明后定义变量的使用规则不同,auto声明的变量必须被初始化,以使编译器能够从其初始化表达式中推导出其类型。从这个意义上来讲,auto并非一种“类型”声明,而是一个类型声明时的“占位符”,编译器在编译时期会将auto替代为变量实际的类型。
auto优势
-
在拥有初始化表达式的复杂类型变量声明时简化代码
void loopover(std::vector<std::string> & vs) { std::vector<std::string>::iterator i = vs.begin(); // iterator,想说爱你并不容易 for (; i < vs.end(); i++) { // 一些代码 } }
以上代码可以优化为:
void loopover(std::vector<std::string> & vs) { for (auto i = vs.begin(); i < vs.end(); i++) { // 一些代码 } }
-
可以免除程序员在一些类型声明时的麻烦,或者避免一些在类型声明时的错误
-
其“自适应”性能能够在一定程度上支持泛型的编程
template<typename T1, typename T2> double Sum(T1 & t1, T2 & t2) { auto s = t1 + t2; // s的类型会在模板实例化时被推导出来 return s; } int main() { int a = 3; long b = 5; float c = 1.0f, d = 2.3f; auto e = Sum(a, b); // s的类型被推导为long auto f = Sum(c, d); // s的类型被推导为float }
-
auto还会在一些情况下取得意想不到的好效果
#define Max1(a, b) ((a) > (b)) ? (a) : (b) #define Max2(a, b) ({ \ auto _a = (a); \ auto _b = (b); \ (_a > _b) ? _a: _b; }) int main() { int m1 = Max1(1*2*3*4, 5+6+7+8); int m2 = Max2(1*2*3*4, 5+6+7+8); }
相比传统的三元运算符表达式,Max2中将a和b都先算出来,再使用三元运算符进行比较,提高了计算性能。
auto使用细则
-
auto类型指示符与指针和引用之间的关系
int x; int * y = &x; double foo(); int & bar(); auto * a = &x; // int* auto & b = x; // int& auto c = y; // int* auto * d = y; // int* auto * e = &foo(); // 编译失败, 指针不能指向一个临时变量 auto & f = foo(); // 编译失败, nonconst的左值引用不能和一个临时变量绑定 auto g = bar(); // int auto & h = bar(); // int&
上文中可以看出,对于a,c,d三个变量而言,声明其为auto *或auto并没有区别,而如果要使得auto声明的变量是另一个变量的引用,则必须要使用auto&,如同本例中的变量b和h一样。
-
auto与volatile和const之间也存在着一些相互的联系
C++11 标准规定auto可以与cv限制符一起使用,不过声明为auto的变量并不能从其初始化表达式总“带走”cv限制符。double foo(); float * bar(); const auto a = foo(); // a: const double const auto & b = foo(); // b: const double& volatile auto * c = bar(); // c: volatile float* auto d = a; // d: double auto & e = a; // e: const double & auto f = c; // f: float * volatile auto & g = c; // g: volatile float * &
其中变量d,f却无法带走a和f的常量性或者易失性。不过声明为引用的变量e,g都保持了其引用的对象相同的属性(事实上,指针也是一样的)。
auto无法使用的情况
auto受制于语法的二义性,或者是实现的困难性,auto往往也有使用上的限制。
void fun(auto x =1){} // 1: auto函数参数,无法通过编译
struct str{
auto var = 10; // 2: auto非静态成员变量,无法通过编译
};
int main() {
char x[3];
auto y = x;
auto z[3] = x; // 3: auto数组,无法通过编译
// 4: auto模板参数(实例化时),无法通过编译
vector<auto> x = {1};
}
- 从fun函数可以看出,auto是不能做形参的类型的
- 编译器阻止auto对结构体中的非静态成员进行推导,即使成员拥有初始值
- 声明auto数组。x是一个数组,y的类型是可以推导的,是一个char数组类型,而声明auto z[3]这样的数组同样会被编译器禁止
- 在实例化模板的时候使用auto作为模板参数,虽然可以一眼而知是int类型,但编译器却阻止了编译