C++数组剖析
创始人
2024-02-12 19:14:21
0

你真的清楚数组吗?我们还是以题目的方式来讲解

开胃题

1. 数组是什么类型?

数组的类型就是数组,数组本身就是一个类型,任何不把数组当做类型都是愚蠢的,同时数组边界也是数组类型的一部分,我们举个例子。

int main() {std::is_same_v;//truestd::is_same_v;//false
}

2. 以下代码的T是什么类型?

int main() {const char array[10]{};using T = decltype(array[0]);
}

答案: const char&。

解释:

对数组类型(通过 typedef 或模板操作)应用cv 限定符会将限定符应用到它的元素类型,但元素是有 cv 限定类型的任何数组类型都会被认为拥有相同的 cv 限定性。如果在C语言,直到C23在明确这一概念。

始终认为数组类型与其元素类型有等同的限定,除了始终不认为数组类型有 _Atomic 限定:(C23 起

3. 下面的字符串字面量是什么类型?T是什么类型?

int main() {using T = decltype(("***"));
}

答案: 字符串字面量的类型是const char[4],T的类型是const char(&)[4]。

解释: 这两个看似是一个问题,其实不对,这里其实还考察了你对decltype这个关键字的了解。

字符串字面量的类型标准早有规定是**const char[N],N表示大小,并且字符串字面量属于左值,**那么按照decltype的规定,自然也推导出左值引用了。

第二轮

1. 下面这段代码是否通过编译?什么环境可以?这是什么?

#include
struct test
{int a;double b;char c[0];
};
int main() {auto t = (test*)malloc(sizeof(test) + 27 * sizeof(char));memset(t->c, 0, 27);//记得置0std::cout << sizeof * t << std::endl;for (int i = 0; i < 26; i++) {t->c[i] = 'A' + i;}std::cout << t->c << std::endl;free(t);
}

打印:16 ABCDEFGHIJKLMNOPQRSTUVWXYZ

答案:可以通过编译,经测试在vs2022和GCC12.2下,这是柔性数组。

解释: 可能这里有很多人会有疑问,C/C++明确规定不允许定义长度为0的数组,为什么这里却可以?

非标准扩展,不是c++的东西。

柔性数组成员允许结构中包含一个大小可变的数组。柔性数组成员只作为一个符号地址存在,而且必须是结构体的最后一个成员,sizeof 返回的这种结构大小不包括柔性数组的内存。

柔性数组成员不仅可以用于字符数组,还可以是元素为其它类型的数组。包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

我们必须得提一下,其实柔性数组这个东西是c99添加到c语言标准的,它实际上不算什么扩展,但是是结构体最后成员拥有不完整的数组类型,我们上面写的是大小为0的,所以我说那种是非标准扩展,并**不是说柔性数组是非标准的,**如果你有别的想法可以评论。文档说法见下

https://zh.cppreference.com/w/c/language/structzh.cppreference.com/w/c/language/struct

2.下面的代码能否正常运行?在什么环境下?它是什么?

#include"iostream"
int main(){int n=10;int array[n];for(auto& i:array){i=6;}for(const auto&i:array){std::cout<

答案: 可以正常运行,在支持c99标准的编译器下,变长数组或者叫非常量长度数组是c99添加的,但是msvc不支持。

解释:

表达式 不是整数常量表达式,则数组声明器声明一个非常量大小的数组( VLA )。

3. 下面的代码能否正常运行?原因?

#include"iostream"
int main(){int n = 1;label:;int a[n]; // 重分配 10 次,每次拥有不同大小printf("The array has %zu elements\n", sizeof a / sizeof *a);if (n++ < 10) goto label; // 离开作用域的 VLA 结束其生存期
}

答案: 能。

解释: 每次控制流经过该声明时,会求值 表达式 (而且它必须每次求值为大于零的值),然后分配数组(对应地, VLA 的生存期在其声明离开作用域时结束)。 VLA 实例的大小不会在其生存期改变,但在另一次经过同一代码时,它可能被分配不同大小

4.下面代码能否正常运行?原因?

#include"stdio.h"
void foo(size_t x, int a[*]);
void foo(size_t x, int a[x])
{printf("%zu\n", sizeof(a)); // 大小同sizeof(int*)
}
int main(){size_t n=10;int array[n];foo(n,array);
}

答案:能,原因如下。

解释:若大小是*,则声明是对于未指定大小的 VLA 的。这种声明只能出现于函数原型作用域,并声明一个完整类型的数组。其实,所有函数原型作用域中的 VLA 声明器都被处理成如同用*替换表达式友情提示,这种形式只能是C,而不是C++,经测试在gcc12.2的c++环境下不行,得c。

5.下面声明的数组,哪些错误,哪些正确?原因?

extern int n;
int A[n];            
extern int (*p2)[n]; 
int B[100];          
void fvla(int m, int C[m][m]); 

答案: 第一第二错误,第三第四对。

解释:非常量长度数组与从它们派生的类型(指向它们的指针,等等) 被通称为“可变修改类型”( VM )。任何可变修改类型的对象只能声明于块作用域或函数原型作用域中。

6.下面哪几个声明的数组正确,哪些错误,为什么?

int main(){int n=10;static int array[n];extern int array_[n];int array__[n];
}

答案: 第一第二错误,第三个正确。

解释:VLA 必须拥有自动或分配存储期。指向 VLA 的指针,但不是 VLA 自身亦可拥有静态存储期。 VM 类型不能拥有链接。。

7.下面的代码能否正常运行?为什么

struct tag {int z[n]; int (*y)[n]; 
};

答案: 不能。

解释: 可变修改的类型不能是结构体或联合体的成员。

8.下面的代码是否正确,为什么?

struct test
{int a;double b;char c[];
};

答案: 正确。

解释: 在struct定义中,未知大小数组必须出现作最后一个元素(只要有一个具名成员),这种情况下,这是称为柔性数组成员的特殊情形。C99起。同时按照划分,柔性数组也算未知大小的数组,我们之前写的柔性数组是大小为0的,其实都可以。

9.下面的代码是否正确,为什么?

//test.cpp
int array[6]{ 1,2,3,4,5,6 };//main.cpp
extern int array[];
int main() {for (size_t i = 0; i < 6; i++)std::cout << array[i] << ' ';
}

答案: 可以。

解释:若忽略数组声明器中的表达式,则它声明一个未知大小数组。除了函数参数列表中的情况(这种数组被转换成指针),而且当初始化器可用时,这种类型是一个**不完整类型。**

10.下面代码能否正常运行,为什么?

int main() {int array[10]{};int array_[10]{};array = array_;
}

答案:不可以。

解释: 数组类型的对象不是可修改左值,尽管它们可以取地址,它们不能出现于赋值运算符的左侧。

11.下面代码哪些能正常运行,为什么?

#includevoid f(int(&& x)[10])
{std::cout << sizeof x << '\n';
}
int main() {using Type = int[];using Type2 = int[10];auto p = Type{1};auto p_ = Type2{1};auto p__ = Type2{};auto p2 =Type(10);auto p2_ =Type2(10);auto p2__ = Type2();f(Type2{10});//√f(Type2{});//√f(Type2(10));f(Type2());
}

答案: 在msvc下全部正常运行,在GCC下只有f(Type2{10}); f(Type2{});是正确的。

解释:

尽管数组无法从函数按值返回,且不能作为大多数转型表达式的目标,数组纯右值依然可以通过使用类型别名构成,并用花括号初始化的函数式转型来构造数组的临时量。至于编译器的结果,只能说,msvc这些写法基本没有必要,并且也是危险的,比如这里的p都被推导为一个int*的指针,指针指向一个纯右值的数组,显然,如果你在后面使用了这个指针去访问内存,至少是已经UB了。

至于用()而不是{}构造临时量,只能说是msvc允许,但按道理是不对的,也没必要。

12.下面代码是否正确?

int* p = new int[0]; 
delete[] p;

答案: 正确。

解释: 虽然之前说过C/C++标准规定数组大小不能为0,但是在用于[new] 表达式时,数组的大小可以为零;这种数组没有元素。你可能会疑问为什么还要delete?有的时候规定就是如此,你就算是new int[0]也一样调用了operator new等,返回了地址,p也指向了某个玩意,你不用在意,反正正常人不会这么写。

13.T是什么类型?这个+array表达式的值类别是什么?为什么?

int main() {int array[6]{};using T = decltype( + array);
}

答案: T是int*类型,表达式的值类别是纯右值。

解释:

存在从数组类型的左值和右值到指针类型的右值的隐式转换:它构造一个指向数组首元素的指针。凡在数组出现于不期待数组而期待指针的语境中时,均使用这个转换。

你可能会觉得这个+很奇怪,它只是为了创造期待指针的语境从而让数组转换为纯右值仅此而已,类似在对lambda也有效。

14.下列声明了多少个函数?

int f(char s[3]);
int f(char[]);
int f(char* s);

答案: 1个

解释: 每个函数形参的类型根据规定,如果类型是“T 的数组”或“T 的未知边界数组”,那么它被替换成类型“T 的指针”。其实这应该是函数声明的内容,链接如下:函数声明 - cppreference.com

函数声明 - cppreference.comzh.cppreference.com/w/cpp/language/function

15.下列函数是否正确?

void f(int a[0]){}

答案: msvc错误,GCC正确

解释: 你们觉得是谁没遵守标准??

总结:

如果描述有误,请各位提出,窝会修改、、

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
客厅放八骏马摆件可以吗(家里摆... 今天给各位分享客厅放八骏马摆件可以吗的知识,其中也会对家里摆八骏马摆件好吗进行解释,如果能碰巧解决你...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...