C/C++中类型是重要的。在位级上,自然只有0和1,相同的二进制位,按不同的类型来解析,结果显然不一样。
#include <iostream>
using namespace std;
struct A
{
int m_i;
int *m_p;
};
int main()
{
A obj;
int *q = (int *)&obj;
q[1] = -2;
cout << (q + 1) << " "
<< &obj.m_p << endl;
cout << q[1] << endl;
cout << obj.m_p;
return 0;
}
C++标准要求,在同一个访问控制区(private,protected,public),成员的排列需符合“较晚出现的成员在内存中有较高的地址”(参见深度探索C++对象模型,第3.2节)。obj对象中,obj.m_i地址值和obj的地址值相同(注意只是值相同,类型不同)。于是q+1指向的就是obj中m_p,即*(q+1)和obj.m_p对应的位级相同。但由于*(p+1)的类型是int, 而obj.m_p的类型是int*, 所以最终的结果如下:
C/C++是静态类型语言,变量都需要明确的类型来定义。定义时,把变量名拿掉,剩下的就是变量的类型。可通过 #include <typeinfo> 头文件,调用 typeid(对象名).name() 来查看。
比如 char *p; p的类型是char *.
void (*f)(int i, double d); 则f的类型是 void (*)(int, double);
int a[3][4]; 则a的类型是 int [3][4]
对于复合类型,由外到内,层层深入,按优先级越来越高的方向,与变量名结合最紧密(即最内层的,优先级最高的) 的才表明其本质才是啥,即前边的相当于定语, 最终类型是最后的中心词。比如 char*(*a[4])(); 由外到内,先是*, 表明是指针;然后是( ), 表明是函数;接着是*, 表明是指针;最后是[] , 表明是数组。从外到内 指针 函数 指针 数组,即a的本质是数组。于是,总的来说,a的类型是成员为函数指针的数组,函数的返回类型是char * .这么说是将划线的看作一体的,指针 函数 指针 数组。或者可以这样看,指针 函数 指针 数组,那就这样说,a的类型是成员为指向返回值为 char 的指针函数的指针的数组。无论怎么看,先后保持先后顺序不变就行。
可以结合变量名取出一部分类型来,需要注意的是,只能拿表示其本质的,也就是最里边的部分出来。还有一点需要注意的是,*(p+i) == p[i]的等价.
这 个等价关系是有特例的,声明 int func(); int (*pf)() = &func 或者 pf = func; 通过函数指针来调用时, 可以 pf() 也可以是 (*pf)(); 但绝对不能是 (p[0])(), 也就是说此时,*p不能用 p[0]
替代,原因是函数指针不能够进行算术运算。
来 看例子,char *p; 则p的类型是去掉声明时的p,是char *; *p把*和p都取出来,所以类型是 char 。对于int a[3][4], 最内层的是 [3]的那个中括号,所以a[1] 取出之后,剩下的是 int [4] ;即a[1]的类型是int [4], 另外由于*a == a[0], 所以同样的, *a的类型是 int [4].
对于 char *(*b[4])(); 则 b[1] 的类型是剩下的部分 char *(*)() ;等价的,因为b和[]结合最紧,即 *(b+1) 实际上是 b[1] ,所以类型也是 char *(*)() .
*b[1] 的类型是 char * (),,即**(b+1)的类型也是 char * () . 这里需要注意的是 *(b+1) 的类型是指向返回值为char *的函数指针,总之就是某种类型的函数指针,前边说过的,函数指针 pf 只能够 *pf,而不能够 pf[0] . 所以这里如果表达成 b[1][0] 或者 (*(b+1))[0] 都是不合法的表达式,而只能够用 *b[1] , **(b+1);
(*b[1])()的类型是char *, *(*b[4])()的类型自然就是char .
在VS2013下的实验代码:
#include <iostream>
#include <typeinfo>
using namespace std;
char* f1()
{
return (char*)malloc(sizeof(char*));
}
char* f2()
{
return (char*)malloc(sizeof(char*));
}
char* f3()
{
return (char*)malloc(sizeof(char*));
}
char* f4()
{
return (char*)malloc(sizeof(char*));
}
int f()
{
cout << "hello world!";
return 0;
}
class MyClass
{
public:
int m_i = 0; //C++11的就地初始化
void m_func();
};
void MyClass::m_func()
{
cout << "hello world!" << endl;
}
int main()
{
char *(*b[4])() = { &f1, &f2, &f3, &f4 };
cout << typeid(b).name() << endl;
cout << "_______________" << endl;
cout << typeid(b[1]).name() << endl;
cout << typeid(*b).name() << endl;
cout << "_______________" << endl;
cout << typeid(**(b + 1)).name() << endl;
cout << typeid(*b[1]).name() << endl;
//b[1]或者说*(b+1)是某种类型的函数指针
//所以b[1][0] 和 (*(b+1))[0]都是不合法的
//cout << typeid((*(b+1))[0]).name() << endl; //报错
//cout << typeid(b[1][0]).name() << endl; //报错
cout << "_______________" << endl;
cout << typeid((*b[1])()).name() << endl;
cout << "_______________" << endl;
cout << typeid(*(*b[1])()).name() << endl;
cout << "_______________" << endl;
int (*pf)() = f;
cout << typeid(*pf).name() << endl;
//函数指针不能使用中括号来索引调用
//cout << typeid(pf[0]).name() << endl; //报错
cout << "_______________" << endl;
MyClass obj;
int MyClass::*ptom = &MyClass::m_i;
cout << obj.*ptom <<endl;
cout << typeid(ptom).name() << endl;
void (MyClass::*ptomf)() = &MyClass::m_func;
(obj.*ptomf)(); //ptom和ptomf必须和对象或者是对象指针一起使用,不能单独出现
cout << typeid(ptomf).name() << endl;
cin.get();
return 0;
}
输出的结果如下:
对任意变量取地址之后,得到的总是一个指针。即 不对变量 var 的类型是什么, &var的本质是一个指针,在原来var 的类型基础上,加了一个*, 最内层的 *. 所以对于 T var, T可能是个复合类型, & var 的类型是 T (*); 比如int a[3]; 那么 &a的类型是 int (* )[3] . const int i; 那么&i的类型是 const int *, 即常量指针。
最后,指向成员变量的指针并不是指针,不能够单独出现,必须和类的对象或者指向对象的指针一起使用。如同上边的ptom和ptomf。详情可参考C++必知必会。注意代码中ptom的定义和是 int * pt = &obj.i区分开来。
数组指针,函数指针,或者数组的类型(因为有()和[]的存在),不能够使用Type var这种形式来定义变量,(当然typedef定义一个变量类型别名除外, 或者C++11里用using FP = void (*)())。不过new之后,可以接这样的类型。
typedef int(*TYPE)[2];
cout << typeid(new int* [2]).name() << endl;
cout << typeid(new (int [2][3])).name() << endl;
cout << typeid(new int*).name() << endl;
cout << typeid(new TYPE).name() << endl;
cout << typeid(new (int(*)[2])).name() << endl; //这里的小括号是必须的
cout << typeid(new (void(*)())).name() << endl; //这里的小括号也是必须的
//cout << typeid(new ((int*)[2])).name() << endl; 这么写是不对的
可以看到,如果new之后的类型是数组,返回值的类型就是这个数组退化为指针,见这里关于数组的退化点击打开链接。(二维数组本身是数组的数组,所以返回值是数组指针)。其他时候,new的返回结果是原来类型的指针(相当于加了一个最内层的指针)。
当涉及到C++多态时,就会有静态类型与动态类型之分了,此时是指使用基类的指针或者引用来指向派生类的对象:
class B
{
int b;
public:
virtual ~B(){ cout << "B::~B()" << endl; }
};
class D : public B
{
int i;
public:
virtual ~D() { cout << "D::~D()" << endl; }
};
D obj;
B *pb = &obj;
cout << typeid(*pb).name() << endl;
cout << sizeof(*pb);
这里,typeid(*pb).name()输出的类型是 D,也就是运行时实际所指对象的类型,但是 sizeof(*pb) 输出的大小却是 8(B类型的对象是8),而不是D类型的对象是12. 这是因为,sizeof 所求的是静态类型的大小,而typeid 取的却是动态的类型。sizeof() 得到的是编译期的常量,只会求表达式的类型,并不会对表达式求值。表达式最后不会执行,因为在编译器就会被常量替换。