网站建设质量体系审核指导,中建八局一公司招聘信息,app免费制作网站哪个好,wordpress分享后可见先序文章请看 盘一盘C的类型描述符#xff08;一#xff09;
稍微组合一下的复杂类型
数组指针类型的数组类型
数组的指针类型我们已经了解了#xff0c;那么#xff0c;以这种类型作为元素的数组类型怎么搞#xff1f;
using type int (*)[3];
// 元素类型是数组指针…先序文章请看 盘一盘C的类型描述符一
稍微组合一下的复杂类型
数组指针类型的数组类型
数组的指针类型我们已经了解了那么以这种类型作为元素的数组类型怎么搞
using type int (*)[3];
// 元素类型是数组指针的数组类型
type arr[4]; // 直接写是什么样呢为了说明问题这里用了using语句后面章节会详细介绍。对于arr它应该是什么类型呢其实应该是
int (*arr[4])[3];同理从里向外最内层的括号里出现了*和[4]表示arr是拥有4个元素的数组类型并且数组元素为指针没有括号分辨内外的情况下切记左为外右为内。而外层的int [3]就表示指针的解类型是一个数组类型。所以我们说arr是一个数组其元素是指针类型指针的解类型为另一种数组类型。
以此为引子我们还能拓展出很多其他更BT的组合不过在继续发散之前我们需要先来看另一个问题。
没有变量名单说类型
如果我问上一节示例中的arr是什么类型呢很简单去掉变量名剩下的部分就是它的「类型描述符」也就是int (*[4])[3]。正推很容易但是反过来可能就让人有点摸不着头脑比如说请问下面类型表示什么含义
int (**)[3];
int *(*)[3];
int **[3];当去掉变量名后这个单纯的类型描述符就显得很诡异尤其是这个小括号搞得乍一看跟什么奇怪的密文一样。遇到这种情况其实也不用慌我们需要「脑补」一个变量名再就好解释了。
变量名添加的位置一定是最内层我们找到最内层的部分分别把变量名补进去再来解释它的类型就好
int (**p1)[3];
int *(*p2)[3];
int **p3[3];所以p1是指针类型其类型为另一个指针类型其解类型为拥有3个int元素的数组类型。或者说p1是「(数组的指针) 的指针」类型数组的二级指针。
p2是指针类型其解类型是一个数组含有3个指针类型元素其解类型是int。或者说p2是「(指针数组) 的指针」类型数组的一级指针。
p3是数组类型拥有3个指针类型的元素其解类型是另一个指针类型其解类型是int。或者说p3是「(指针的指针) 的数组」类型二级指针的数组。
typedef与using
类型复杂了以后可读性会直线下降所以这时候更加推荐在合适的层级进行类型重命名。typedef是从C语言继承来的语法但它有一个缺点就是新的类型名在类型描述符中处于「变量名」的位置看起来会有些奇怪比如说
typedef int type1[3]; // type1其实就是int[3]类型
typedef int (*type2)[2]; // type2其实就是int (*)[2]类型
typedef int *(*type3[4])[5]; // type3其实就是int *(*[4])[5]类型而using则是完全把新的类型名从原本的类型说明符中提取出来了看上去会更直观一些
using type1 int [3];
using type2 int (*)[2];
using type3 int *(*[4])[5];需要注意的是两种用法的区别仅仅在于描述方式其效果是完全等价的。实际使用中不用太过纠结选哪一种都行。
多维数组类型
前面我们介绍了多级指针其本质并没有几级而是说指针的解类型还是一个指针罢了。
同理所谓的「多维数组」并不会真的给出一个多维空间 其本质是「数组的数组」或者说「元素为数组类型的数组类型」。
举例来说
int arr1[2][3];我们说arr1是一个拥有2个元素的数组类型最内层才是本质元素类型是一个有3个int元素的数组类型外层表示的是数组的元素类型。如果我们拆开来写应该是这样的
using ele_t int [3];
ele_t arr1[2];所以我们搞清它的本质那么其他的问题就容易解释了比如说
auto p arr1; // 请问p是什么类型既然数组类型可以隐式转换为首元素的指针那么这里的p应该就是「(数组的元素)的指针 类型」。那么arr1的元素应该是ele_t类型所以p应该是这种类型的指针类型也就是ele_t *类型也就是int [3]的指针类型也就是int (*)[3]类型。
再比如说
auto p2 arr1; // 请问p2是什么类型这里并不是用数组直接转换而是取了地址那么p2就应该是一个二维数组的指针类型了。因为arr1是ele_t [2]类型那么p2就应该是它的指针类型也就是ele_t (*)[2]类型也就是int (*)[2][3]类型。
希望读者可以搞清指针和数组的类型描述方法对于由他们组合的复杂类型也就能见招拆招迎刃而解了。
函数类型和函数指针类型
对于纯C语言程序员来说可能都不容易意识到「函数类型」这种类型毕竟你不会用一个函数的类型去定义另一个函数也不会关心它的大小之类的事情。但是有了C的模板以后这件事就变得值得关注了。
「函数类型」也是一种类型它跟数组类型的描述比较类似包括两部分函数返回值类型和函数的参数类型。
int f1(int, double); // f1是函数类型返回值类型为int接收2个参数分别为int、double类型
void f2(); // f2是函数类型返回值类型为空参数为空
int f3(int); // f3是函数类型返回值类型为int接收1个参数是int类型有一个需要注意的是如果函数无返回值那么必须要用void占位而不可以省略在早期版本标准里返回值为int是可以省略的也就是说
f();
// 相当于
int f();不过这种标准已经废除也不被推荐但自始至终void都不能省略的。
而如果一个函数不接收参数那么小括号中可以空着或者写上void也就是说
void f();
// 相当于
void f(void);函数类型之所以也能成为一个类型主要是由于冯·诺依曼体系的计算机中在存储上并不区分「数据」和「指令」。函数在编译后其实就会成为一段指令同样会加载到内存中同样会拥有地址。那么从本质上来说它就跟变量是一样的这就是函数类型。
有了前面复杂类型的洗礼那么这里我们就可以稍微添加一点难度了。如果要写一个返回值为数组指针类型的函数要怎么办我们观察到在函数类型的说明符里返回值在左边参数在右边。而前面我们已经介绍过没有括号区分的情况下左边是外边右边是里面也就是说返回值应该写在外面参数应该写在里面。那么既然返回值也是一个复杂类型那么就应当把这个类型套在函数类型的外面用于表示返回值类型
int (*f())[3]; // 表示f是一个函数无参数返回值类型是一个指针指针的解类型是int [3]
// 分开来写就是
using ret_t int (*)[3];
ret_t f();通俗来讲就是说f是一个返回值为「数组指针」类型的函数。
既然函数类型也是一种数据类型函数也拥有内存地址那么自然我们就可以取这种类型的指针也就是我们常说的「函数指针」类型。
书写的思路一样本身是一个指针所以星号在最内层紧贴变量名解类型是函数类型那么就把函数类型的描述符写在外层
void (*p)(int); // 表示p是一个指针解类型是函数类型void (int)
// 分开来写就是
using func_t void (int);
func_t *p;不过有一点比较特殊的是函数类型可以隐式转换为函数指针类型而函数取地址也能得到函数指针类型。这样就会造成下面这种很有意思的情况
void f();void Demo() {auto p1 f; // p1是void (*)()类型auto p2 f; // p2也是void (*)()类型auto p3 *f; // p3也是void (*)()类型auto p4 *****************f; // p4也是void (*)()类型auto p5 f; // p5是void (**)()类型「二级指针」
}离谱归离谱但……C嘛DDDD~
上面也展示了函数指针的指针了那我们再来个难一点的一个函数的返回值是函数指针的情况应该怎么写呢
using ret_t void (*)();ret_t f(); // f的类型是思路都没有变返回值套在外面就好不再赘述了
void (*f())(); // 外层的void ()表示指针的解类型内层的*表示f的返回值是指针f后面的()是f的参数列表。非静态成员函数类型
这个标题已经比较自洽了所谓「非静态成员函数(non-static member function)」又有地方管它叫「方法(method)」指的就是类或结构体/共合体中的成员函数并且没有用static修饰的。举例来说
struct Test {void f();
};此时的f就是一个非静态成员函数它的类型是
void (Test::)();先别急我们来慢慢解释~~
首先我们要明确一件事所谓非静态成员函数之所以是一个独立的类型主要是因为它会隐含一个函数参数也就是this指针所指的对象。非静态成员函数不能够直接调用而是要通过一个「对象」作为发起方。用上面的例子来说
void Demo() {Test::f(); // 这种调用方法是错误的Test t;t.f(); // 必须要有发起方
}那么既然有「发起方」那么这个发起方一定有自己的类型。发起方的类型需要与所调用的函数所在类型相匹配也就是说一个对象只能调用自己类或自己的父类包括间接父类中的非静态成员函数并且当符合默认情况时可以省略类名我们来看一个具体的例子
struct Base {void f1();
};struct Test1 : Base {void f2();
};struct Test2 {};void Demo() {Test1 t1;Test2 t2;t1.Base::f1(); // 调用父类的成员函数OKt1.Test1::f2(); // 调用自己类的成员函数OKt1.f1(); // 省略类名则会向上查找到继承链中最近的方法实现这里相当于t1.Base::f1()t1.f2(); // 省略类名同理这里相当于t1.Test1::f2();t2.f1(); // 报错应为Test2的继承链中找不到f1函数t2.Base::f1(); // 同样报错因为t2不属于Base类的继承链中的类型不能够用它来调用Base类的成员函数
}所以对于非静态成员函数来说它的发起方类型也就是隐含参数的类型也应当体现在它的类型描述符中。因此对于「一个Test1类中的成员函数返回值为void参数为空其实是有一个隐含参数的这里说的是不包括隐含的只看明面的情况它是空」这种类型的函数其类型描述符是void (Test1::)()。
或者也可以从另一个角度来解读就是隐含参数是Test1类型而隐含参数的类型要放在函数名的前面也就是最内层的部分。总之这里希望读者知道的是非静态成员函数的类型描述符需要出现这个类型名用以表示隐含参数的类型当然对于继承链下游的类型也同样支持这种情况我们认为做了一次static_cast就好也可以解释。
再多啰嗦一句我们刚才说的都是非静态成员函数的情况而静态成员函数由于不含隐含参数因此它的描述方式跟普通函数是一样的
struct Test {static void f1(); // f1的类型是void ()void f2(); // f1的类型是void (Test::)()
};那么如果非静态成员函数还含有一些其他的属性比如说constnoexcept的话它的声明位置同样在类型的最后也就是参数列表的后面
struct Test {void f1() const;void f2(const int *) ;int f3() noexcept;void f4(int) noexcept;
};上面例子中f1~f4的类型分别是
void (Test::)() const; // f1的类型
void (Test::)(const int *) ; // f2的类型
int (Test::)() noexcept; // f3的类型
void (Test::)(int) noexcept; // f4的类型那么如果一个非静态成员函数的返回值是一个复杂类型呢同理放到外层就好了只要把握住内外层那么所有的问题都能迎刃而解比如说
using type1 int (*)[3];
using type2 void (*)(int);
struct Test {type1 f1() const;type2 f2(double) noexcept;
};对应的类型是
int (*(Test::)() const)[3]; // f1的类型
void (*(Test::)(double) noexcept)(int); // f2的类型不过需要大家知道的是「非静态成员函数」类型只是一个概念上的类型实际情况下我们是没法用这种类型来定义变量的甚至都没法直接定义这种类型
using type void (Test::)(); // 报错
void (Test::f)(); // 报错而能够使用的类型则是它的指针类型下一节来介绍。
非静态成员函数指针类型
既然非静态成员函数基本可以等价于一个隐藏了调用方这个参数的函数类型那么它本质还是一个函数也就是一个代码段自然也是要入内存的。所以它同样含有内存地址也就含有对应的指针类型也就是非静态成员函数指针类型。
同样在最内层加一个指针符号即可表示函数指针类型
struct Test {void f();
};void Demo() {auto fp Test::f;// fp的类型就是非静态成员函数指针类型等价于void (Test::*fp)() Test::f;
}在上面的例子中fp的类型是void (Test::*)()类型。
照例我们还是做一些组合下面直接给出一些例子就不再赘述
struct Test {void f1() const;int f2() noexcept;int f3(double, int) ;
};void Demo() {using type1 void (Test::*)() const;using type2 int (Test::*)(double, int) ;using type3 decltype(Test::f2);type1 *p1; // void (Test::**)() consttype3 p2; // int (Test::*)() noexcept;type2 arr[2]; // int (Test::*[2])() noexcept;type3 (*p3)(int); // int (Test::*(*)(int))(double, int) ;
}顺便啰嗦一句非静态成员函数指针的大小为2个指针的大小在64位环境中应该是16个字节读者可以自行sizeof来验证这部分详细解释可以看深入C成员函数及虚函数表。
【第三篇待更】