数组
数组中每个元素都是对象,即占有特定类型的内存空间。
数组名可以转化为这个数组对象的首个元素的地址。
这里我们不去讨论一维数组,直接从二维说起。所谓二维数组也是数组,只不过它的元素也是一个数组。
首先我们写一个二维数组留作使用
#include<iostream>
using namespace std;
int a[][10]={
{1,2,3,4,5,5,6,7,8,8},
{10,12,32,42,51,15,16,71,121,18}
};
既然说到数组名是其首个对象的地址那么来验证一下,测试数组名,以及对数组名求地址:
void test01(){
cout << (long long)a << endl; // 140273290059808
cout << (long long)(a+1) << endl; // 140273290059848
// 相差40个字节
cout << (long long)&a << endl; // 140273290059808
cout << (long long)(&a+1) << endl; // 140273290059888
// 相差80个字节
}
a 与 a + 1 正好相差40个字节,代表 a 是这个一整行对象的地址,即首行地址。
取址符取得是整个对象的地址,&a 是对二维数组求址,自然偏移一位就变成了整个二维数组的尾地址,c++中的尾地址是对象所在地址的下一位。&a+1 正好比 a 多了 80 个字节。
也就是,数组名 a 自动转化成指向第一个元素的指针, 那么这个指针是什么类型呢?
编译器会把它识别成 int (*) [10]。也即这个指针指向了第一维数组,数组作为对象,占有一块数组类型的内存空间。因为对象是指一块能存储数据并且具有某种类型的内存空间。
我们解引用一下:
void test03(){
cout << *a << endl; // 0x7f051ce02020
//为了验证,我们偏移一下
cout << *(a + 1) << endl; // 0x7f051ce02048
// 正好相差40个字节
经过一次偏移正好相差了 40 个字节,变为第二个元素对象
第二层解掉呢?a作为指针,是一个十个元素的对象地址,解一次引用得到这个对象,由于这个对象还是个数组,也会自动转化为指向自己的首个对象(10个元素的第一个元素的位置)的指针。
因此第二层解掉自然就是第一个 int 型的元素:
cout << *(*a) << endl; // 1 **a 即可
因为指针指向数组对象时,可以用下标访问下一个位置,又 a 是指针指向了数组,下一个偏移为 0,即 * a = * (a + 0) ,可以写成:
cout << *a[0] << endl; // 1
基于上述, *a 也就是a[0],也会自动转化为指向自己的首个对象(10个元素的第一个元素的位置)的指针。所以 a[0] 可以用下标访问数组对象(10个元素)内其他元素:
// cout << (a[0])[0] << endl;
cout << a[0][0] << endl;// 1
我们多搞几个案例:
// 要是访问当前行的下一个元素呢?将这个首地址
cout << *(*a + 1) << endl;// 2 即 *((*a) + 1)
// 请注意这里的指针是 (*a),
cout << (*a)[1] << endl; // 2
// 同理(*a)相当于*(a + 0) 即a[0]
cout << a[0][1] << endl; //2
// 如果访问下一行呢?
cout << **(a+1) << endl;
cout << *a[1] << endl;
cout << a[1][0] << endl; // *(a[1] + 0)
// 第二行第二个元素呢?
cout << *(*(a+1) +1 ) << endl;
cout << *(a[1] +1) << endl;
cout << a[1][1] << endl;
基于上述推导,我认为,下标是就是为了让数组不在抽象,好表达。
于是a[0] 就表示a指向的第一个元素,a[1]就是下一个。感觉C++中在编程中应该尽量避免用使用指针。但这里作为没怎么用过C++的我来说还是得熟悉一下的。
cout << *a << endl; // 0x7f051ce02020
cout << *(a + 1) << endl; // 0x7f051ce02048
cout << a[0] <<endl; // 0x7f051ce02020
cout << a[1]<< endl; // 0x7f051ce02048
为了进一步探讨,我们看一下类型:
cout << typeid(*a).name()<< endl; // A10_i
cout << typeid(&a[0]).name()<< endl; // PA10_i
由此我们可以看出 取址符是会包含对象整体的信息, 是整体的地址,如下也是一样:
cout << typeid(a).name()<< endl; // A2_A10_i
cout << typeid(&a).name()<< endl; // PA2_A10_i
A2_A10_i:由多维数组是数组的数组猜有大到小的猜,A: 这个是数组类型,是两个对象组成的数组,每个对象是由有10个对象的数组,这10个对象是int型的
PA10_i :是一个指针类型, 指向一个数组对象,这个数组对象有10个int型的对象
PA10_i 编译器会把它识别为 int (*)[10],翻译成人话应该是一个指向具有10个元素的数组的指针类型,可以通过试错得到哦:
比如:
int *p = a;// cannot convert ‘int (*)[10]’ to ‘int*’ in initialization
由下面也可以看出, 数组名会自动转成首个元素的地址
int (*p)[10] = a;
cout << typeid(p).name()<< endl; // PA10_i
cout << typeid(&a[0]).name()<< endl; // PA10_i
int (*ptr)[10] = &a[0];
cout << typeid(ptr).name()<< endl; // PA10_i
}
数组指针
一般声明数组的时候都有:
int arr[10]; //
int * pArr[10]; //数组里面装的指针
如何声明一个指向数组对象的指针呢?其实刚才已经一不小心就把 int (*)[10] 这个类型引入出来了,通过typeid 我们又发现他是PA10_i,照书上说的由内向外,以外国人的思维,先说重点的后说细节的翻译:这是一种指针(P),指向了一个数组对象(A),数组里面有10个小对象,这些小对象是int型。
int (*p)[10];
int (&ref)[10] = a[1]; //将ref 引用绑定到a[1]这个对象 ,注意了是指第二行整体。
如果换一个想法的话好像更好理解,括号运算符先执行,这个参数解引用后是一个数组对象,这个数组对象有10个元素。
由此看来,对于数组来说,声明都是这个调调,比普通变量声明多了一个维度说明
举例:
void test04(){
for(int (*p)[10]= a; p!= a + 2; p++){
for(int *q= *p; q != *p + 10; q++)
cout << *q << " ";
cout << endl;
}
// 1 2 3 4 5 5 6 7 8 8
// 10 12 32 42 51 15 16 71 121 18
}
可以用类型别名来简化
typedef int int_array[10];
using intArray= int[10];
于是:
void test05(){
for(int_array *p = a; p!= a + 2; p++){
for(int *q= *p; q != *p + 10; q++)
cout << *q << " ";
cout << endl;
}
// 1 2 3 4 5 5 6 7 8 8
// 10 12 32 42 51 15 16 71 121 18
}
void test06(){
for(intArray *p = a; p!= a + 2; p++){
for(int *q= *p; q != *p + 10; q++)
cout << *q << " ";
cout << endl;
}
// 1 2 3 4 5 5 6 7 8 8
// 10 12 32 42 51 15 16 71 121 18
}
用auto类型说明符,更方便点,但是,这里必须明确,row是对a每一行的引用。如果没有&,row被转化为数组名,也就是每一行第一个元素的地址,不具备迭代功能:
void test07(){
for(const auto &row: a){
for (const auto col: row)
cout << col << " ";
cout << endl;
}
// 1 2 3 4 5 5 6 7 8 8
// 10 12 32 42 51 15 16 71 121 18
}
如何让一个函数返回数组指针:,利用之前的思路: int (*p)[10], 我们很快能理解,让对一个func返回的结果解引用后是一个数组:
Type (*func( paramlist))[dimension];
int (*altArray(int i))[10] // // 这里想用i去替换数组里的数,并且返回一个新的数组,当然也会改变原来的数组,这里只是为了返回一个数组玩玩
{
for( int(&row)[10] : a)
{
for ( int &col: row)
col=i;
}
return a;
}
// 当然可以简化一下
int (*altArrayI(int i))[10]
{
for( auto &row : a){
for ( int &col: row)
col=i;
}
return a;
}
// 类型别名:
intArray *altArrayIII(int i){
for(intArray &row : a){
for ( int &col: row)
col=i;
}
return a;
}
我们也可以用 decltype 简化类型名, 注意:decltype 并不将数组类型转成对应的指针,但是我们可以用取址符得到这一指针
decltype(&a[0]) altArrayII(int i)
{
for(auto &row : a){
for ( int &col: row)
col=i;
}
return a;
}
还有一种就是在leetcode中经常见到的位置返回类型
auto altArrayIV(int i)->int(*)[10]
{
for(auto &row : a){
for ( int &col: row)
col=i;
}
return a;
}
我们用数组指针传参:
void printArray1(intArray *p){
// 利用for读出看看:
for(int i =0;i<2;++i){
for(int j=0; j<10;j++){
// p 此时就是 &a[0]
// p+i 就是&a[i]
// 解引用就是 a[0],a[i],
// 就是说(*p/a[0])指向了一个数组的首个元素的地址
// 即 &a[0][0], &a[i][0]
// 再解一次就是a[0][0]
// 所以有:
// cout << *(*(p + i) +j) << " ";
cout << p[i][j] << " ";
}
cout << endl;
}
cout << endl;
}
测试:
void test08(){
intArray * p = altArray(10);
intArray * p1 = altArrayI(10);
intArray * p2= altArrayII(10);
intArray * p3 = altArrayIII(10);
intArray * p4 = altArrayIV(10);
printArray1(p);
printArray1(p1);
printArray1(p2);
printArray1(p3);
printArray1(p4);
}
函数指针
我们声明一般是这样的:
int arr[10];
int * pArr[10];
int (*p)[10];
int func1(int i);
int *func2(int i);
int &func3(int i);
int (*altArray(int i))[10];
那如何声明一个函数指针呢?
// 书上的例子
bool lengthCompare(const string &, const string &);
bool (*pf)(const string &, const string &);
就是解引用以后是一个bool函数;也就是一个指向bool函数的指针。
如何初始化呢?龟龟,与int (*p)[10]; 好像啊,都是具有自己的特点,函数就是带参数列表的变量,数组就是带维度的变量,这么说应该也没错吧。
void test09(){
void (*printf)(intArray *) = printArray1;
(*printf)(a);
// 1 2 3 4 5 5 6 7 8 8
// 10 12 32 42 51 15 16 71 121 18
}
函数指针形参
可以用指针表示 当然也可以传递,我们重载一下 altArrayIII , 测试将输出数组的函数printArray1的指针传给它:
// 类型别名:
typedef void (*PrintArray)(intArray *) ;
intArray *altArrayIII(int i, PrintArray pfa){
cout << "修改前:" << endl;
pfa(a);
for(intArray &row : a){
for ( int &col: row)
col=i;
}
cout<< "修改后:" << endl;
pfa(a);
return a;
}
// 测试:
void test10(){
// void (*printf)(intArray *) = printArray1;
// 函数名类似数组名也可以转化为地址:
altArrayIII(15, printArray1);
}
返回函数的指针
这一块在函数声明了解以后应该不难理解,目的是让一个函数返回一个函数的指针, 用个小栗子:
int myAdd(int a, int b){
return a + b;
}
int mySub(int a, int b){
return a - b;
}
using Fp = int(*)(int, int); // 确定一下类型,等会好操作
Fp test(bool isSub) // 要返回一个指针,指针解引用后是一个函数
{
if(isSub)
return mySub;// 函数名自动转化为地址
else
return myAdd;
}
void test11(){
bool sub = true;
Fp p = test(sub);
int ret = (*p)(10, 15);
cout<<ret << endl;
}
复杂一下,将今天所有的都写上:
void altArrayAndPrint(int i, PrintArray pfa)
{
cout << "修改前" << endl;
pfa(a);
for(intArray &row : a){
for ( int &col: row)
col=i;
}
cout<< "修改后" << endl;
pfa(a);
}
auto testReturnFuncPointer3()->void (*)(int , PrintArray )
{
return altArrayAndPrint;
}
void test12(){
auto testPtr3 = testReturnFuncPointer3();
(*testPtr3)(13, printArray1);
// 修改前
// 1 2 3 4 5 5 6 7 8 8
// 10 12 32 42 51 15 16 71 121 18
// 修改后
// 13 13 13 13 13 13 13 13 13 13
// 13 13 13 13 13 13 13 13 13 13
}
下面是回的四种写法,不用看了。
using AltPtr = void (*)(int , PrintArray );
typedef decltype(altArrayAndPrint) *Func;
typedef void (*Func2) (int , PrintArray );
AltPtr testReturnFuncPointer()
{
return altArrayAndPrint;
}
Func testReturnFuncPointer1(){
return altArrayAndPrint;
}
Func2 testReturnFuncPointer2(){
return altArrayAndPrint;
}
int main(){
// test01();
// test02();
// test03();
// test04();
// test05();
// test06();
// test07();
// test08();
// test09();
// test10();
// test11();
test12();
// AltPtr testPtr = testReturnFuncPointer();
// (*testPtr)(16, printArray1);
// Func testPtr1 = testReturnFuncPointer1();
// (*testPtr1)(15, printArray1);
// Func2 testPtr2 = testReturnFuncPointer2();
// (*testPtr2)(12, printArray1);
return 0;
}