静态类型:使用前需要声明和定义。
动态类型:无需声明直接使用(需要定义)。如Python、Perl、JavaScript
本质区别在于类型检查时机,静态在编译期,动态在运行时(依赖类型推导)
C++11引入了两种类型推导:auto和decltype。两者都是静态类型,因此必须是编译期推导的类型。
而且auto声明必须定义,否则无法推导。实际上auto不是类型,只是类型占位符,在编译期推导成功后进行类型替换。
#include
#include
void loopover(std::vector & vs) {
std::vector::iterator i = vs.begin(); // 想要使用iterator,往往需要
书写大量代码
for (; i < vs.end(); i++) {
// 一些代码
}
}
#include
#include
void loopover(std::vector & vs) {
for (auto i = vs.begin(); i < vs.end(); i++) {
// 一些代码
}
}
对比上述两例代码可以看到使用auto,能够简化代码类型。
class PI {
public:
double operator* (float v) {
return (double)val * v; // 这里精度被扩展了
}
const float val = 3.1415927f;
};
int main() {
float radius = 1.7e10;
PI pi;
auto circumference = 2 * (pi * radius);
}
上述代码radius未float类型,而在经过计算之后得到的circumference精度得到了扩展(double),而有时我们容易依赖原有类型将circumference声明为float类型,这就使得计算结果精度降低了。
而使用auto则会自动推导为double类型。
但是,对于计算溢出的场景,自动推导并不能扩展其类型(因为只能对类型结果推导,而不能对计算结果推导)
#include
using namespace std;
int main() {
unsigned int a = 4294967295; //最大的unsigned int值
unsigned int b = 1;
auto c = a + b; // c的类型依然是unsigned int
cout << "a = " << a << endl; // a = 4294967295
cout << "b = " << b << endl; // b = 1
cout << "a + b = " << c << endl;// a + b = 0
return 0;
}
上述仍然会将c推导为unsigned int类型,而非更大存储范围的unsigned long。
template
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
}
// 编译选项:g++ -std=c++11 4-2-7.cpp
可以自动推导多个输入类型参数计算后的结果类型。当然这里来看仍然需要要求为double类型(因为返回值为double),但是实际上结合后面的decltype可以实现追踪返回类型,从而将返回类型也实现自动推导。
#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);
}
我们定义了两种类型的宏Max1和Max2。两者作用相同,都是求a和b中较大者并返回。前者采用传统的三元运算符表达式,这可能会带来一定的性能问题。因为a或者b在三元运算符中都出现了两次,那么无论是取a还是取b,其中之一都会被运算两次。而在Max2中,我们将a和b都先算出来,再使用三元运算符进行比较,就不会存在这样的问题了
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&
auto不继承引用,所以如果需要引用时需要自己添加,否则可能会引起拷贝,从而导致性能问题。
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 * &
同引用一样,auto不会继承const和volatile关键字,需要自己添加。
#include
#include
using namespace std;
int i = 1;
int & j = i;
int * p = &i;
const int k = 1;
int main() {
auto* v3 = p; // v3的类型是int*
v3 = &i;
}
auto在进行类型推导时,会对auto后冗余的*进行忽略,如p通过自动推导为int *,而auto*则不会被认为是int **,而是int *。
#include
using namespace std;
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 v = {1};
}
// 编译选项:g++ -std=c++11 4-2-13.cpp
1)对于函数fun来说,auto不能是其形参类型。如果需要泛型的参数,还是需要求助于模板。
2)对于结构体来说,非静态成员变量的类型不能是auto的。同样的,由于var定义了初始值,读者可能认为auto可以推导str成员var的类型为int的。但编译器阻止auto对结构体中的非静态成员进行推导,即使成员拥有初始值。
3)声明auto数组。我们可以看到,main中的x是一个数组,y的类型是可以推导的,而声明auto z[3]这样的数组同样会被编译器禁止。
4)在实例化模板的时候使用auto作为模板参数,如main中我们声明的vector