详解C++数组和数组名问题(指针、解引用)
一、指针
1.1 指针变量和普通变量的区别
指针:指针的实质就是个变量,它跟普通变量没有任何本质区别。指针完整的应该叫指针变量,简称为指针。 是指向的意思。指针本身是一个对象,同时指针无需在定义的时候赋值。
1.2 为什么需要指针
指针的出现是为了实现间接访问。在汇编中都有间接访问,其实就是cpu的寻址方式中的间接上。
间接访问(cpu的间接寻 址)是cpu设计时决定的,这个决定了汇编语言必须能够实现问接寻又决定了汇编之上的c语言也必须实现简介寻址。
1.3 指针使用三部曲
三部曲:定义指针变量、关联指针变量、解引用
(1)当我们int *p定义一个指针变量p时,因为p是局部变量,所以也道循c语言局部变量的一般规律(定义局部变量并且未初始化,则值是随机的),所以此时p变量中存储的是一个随机的数字。
(2)此时如果我们解引用p,则相当于我们访问了这个随机数字为地址的内存空间。那这个空间到底能不能访问不知道(也许行也许不行),所以如果直接定义指针变量未绑定有效地址就去解引用几平必死无疑。
(3)定义一个指针变量,不经绑定有效地址就去解引用,就好象拿一个上了镗的枪随意转了几圈然后开了枪。
(4)指针绑定的意义就在于让指针指向一个可以访问、应该访问的地方(就好象拿着枪瞄准且标的过程一样),指针的解引用是为了间接访问目标变量(就好象开枪是为了打中目标一样)
int val = 43; int * p = &val; // &在右值为取值符 cout << *p << endl; //输出 43
二、整形、浮点型数组
前言
- 在很多用到数组名字的地方,编译器都会自动地将其替换为一个指向该数组首元素的指针。
- 所以,在大多数表达式中,使用数组名其实是在使用一个指向该数组首元素的指针。
2.1 数组名其实是特殊的指针
int main() { int a[] = { 0,1,2,3,4 }; printf("%x\n", a); printf("%x\n", &a); printf("%x\n", &a[0]); }
- 从局部变量表可以看出,数组a和指针p的构成是很相似的。它们实际存的都是一个地址,都会指向一个对象(或多个对象的第一个对象)。所以说数组名其实是种特殊的指针。
- 为什么说是特殊呢?
一维数组
int a[] = { 0,1,2,3,4 }; int * p1 = a; int *p = &a[0]; //指针p是 int * 的,而首元素是有地址的,所以取址,是允许的 //int * p1 = &a; //错误 //理解:int (*p1)[5] = &a; //正确 /* 但它俩自身又有不同: 指针 p1 本身是一个对象,在内存中是为其分配了空间的; 数组名 a 在内存空间中没有分配到空间(这将导致&a操作的效果可能和预想的不大一样)。 可以理解为a指向一个含有5个整数的数组的指针,故 &a的类型为int(*)[5],不能用来初始化int */
整理:
指针 | 类型 |
---|---|
a | int * |
&a | int (*) [5] |
二维数组
int ia[3][4]; int (*p)[4] = ia; //ia 的类型就是 int(*)[4] int (*p)[3][4] = &ia; //&ia的类型就是 int(*)[3][4]
整理:
2.2 理解复杂的数组的声明
上述提到数组名是指向一个数组的指针,因此解释一下一些复杂的数组声明。加深理解
int * ptr[10]; //ptr是含有10个 整形指针 的数组 int & ref[10] = /* ? */ //错误,不存在引用的数组 int (*parray) [5] = &a; //parray指向一个含有5个整数的数组 /* 同时也是上述数组名的解释 *parray意味着parray是一个指针; 右边是[5]表明是指向大小为10的数组; 左边int表明数组中元素为int. */ int (&array)[5] = a; //array引用一个含有5个整数的数组 int * (&array) [10] = ptrs; //array是数组的引用, 该数组是含有10个指针的数组
2.3 数组名a、数组名取地址&a、数组首元素地址&a[0]、指向数组首元素的指针*p
int main() { int a[] = { 0,1,2,3,4 }; printf("%x\n", a); printf("%x\n", &a); printf("%x\n", &a[0]); int *p = &a[0]; decltype(a) t; decltype(&a) tt; cout << p << endl; printf("%x,%x\n", a + 1, p + 1); printf("%x\n", &a + 1); cout << sizeof(a) << " " << sizeof(&a) << endl; }
输出
-
a
既然是种特殊的指针,那么其打印时就会是存的地址。 -
&a
的类型是int(*)[5]
(读法从小括号里往外,首先是指针,然后是大小为5的数组,然后数组元素类型是int
),从局部变量中看到其类型也可写成int[5] *
:即指向大小为5的int数组的指针。由于数组名没有内存分配空间 -
&a[0]
就是取一个int对象的地址,这个int
对象是数组首元素。综上所述,就造成了a &a &a[0]
三者打印出来的地址是一样的。 -
p
,指向数组首元素的指针。 -
a + 1,p + 1
都是按照元素大小的字节数(4字节),增加4。 -
&a + 1
,前面说了 &a的类型是指向大小为5的int
数组的指针,大小为5的int
数组所占字节数为20,所以&a + 1
就应该增加20。 -
sizeof(a)
为20,因为数组总的字节大小为20。 -
sizeof(&a)
为4,因为&a
是一种指针,指针在32位系统中占4字节。
2.4 对数组名以及取值符&的理解
数组中每个元素都是对象,即占有特定类型的内存空间。(对象,占有一块数组类型的内存空间。因为对象是指一块能存储数据并且具有某种类型的内存空间。)
数组名可以转化为这个数组对象的首个元素的地址。
这里我们不去讨论一维数组,直接从二维说起。所谓二维数组也是数组,只不过它的元素也是一个数组。
首先我们写一个二维数组留作使用
#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} };
简单说明一下数组:数组a 是包含2个元素的数组,每个元素是一个包含10个 int 的数组。
既然说到数组名是其首个对象的地址那么来验证一下,测试数组名,以及对数组名求地址:
void test01(){ cout << (long long)a << endl; // 140273290059808 cout << (long long)(a+1) << endl; // 140273290059848 // 相差40个字节 }
(用long long
型一眼就能看出是40个字节)
a
与 a + 1
正好相差40个字节,说明:
(1)数组名a 是(首元素{1,2,3,4,5,5,6,7,8,8}
)这一整行对象的地址,即首元素地址;
(2)所以在a+1
偏移了一个元素大小——40字节。
void test01(){ cout << (long long)&a << endl; // 140273290059808 cout << (long long)(&a+1) << endl; // 140273290059888 // 相差80个字节 }
&a
与 &a + 1
正好相差80个字节,说明:
(1)取址符取得是整个对象的地址,&a
是对二维数组求址,针对的是整个对象;
(2) &a+1
偏移一位就变成了整个二维数组的尾地址,c++中的尾地址是对象所在地址的下一位。&a+1
正好比 a
多了 80 个字节。
在上面也提到数组名会自动转换成一个特殊指针(两个表格当中的总结),接下来将理解这个指针到底是什么?
从指针解引用方面理解:
void test03(){ cout << *a << endl; // 0x7f051ce02020 //为了验证,我们偏移一下 cout << *(a + 1) << endl; // 0x7f051ce02048 // 正好相差40个字节 }
*a 把数组名解引用之后是首元素(因为数组名是指向首元素的特殊指针),而首元素也是一个有10个元素的数组,现在 *a
是代表这个对象,输出它就是此数组的首元素——1 的地址.。
cout << *(*a) << endl; //1 **a 即可
第二层解掉:*(*a)
自然就是第一个 int 型的元素。
cout << *a[0] << endl; // 1
因为指针指向数组对象时,可以用下标访问下一个位置,又 a 是指针指向了数组,下一个偏移为 0,即 * a = * (a + 0)
// cout << (a[0])[0] << endl; cout << a[0][0] << endl;// 1
基于上述, *a 也就是a[0],也会自动转化为指向自己的首个对象(10个元素的第一个元素的位置)的指针。所以 a[0]
可以用下标访问数组对象(10个元素)内其他元素:a[0][0] == 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;
查看数组名类型理解
cout << typeid(*a).name()<< endl; // a10_i cout << typeid(&a[0]).name()<< endl; // pa10_i
a10_i :是由10个 int 组成数组
pa10_i :是一个指针类型, 指向一个数组对象,这个数组对象有10个int型的对象。编译器会识别为int(*)[10]
cout << typeid(a).name()<< endl; // a2_a10_i cout << typeid(&a).name()<< endl; // pa2_a10_i
a2_a10_i:由多维数组是数组的数组。a表示这个是数组类型,2表示是两个对象组成的数组,每个对象(a10_i)是由有10个对象的数组,这10个对象是int型的。
pa2_a10_i:是一个指针类型, 指向一个数组对象,这个数组对象有2个数组型的对象。编译器会识别为int(*)[2][10]
三、字符数组数组名
c++对待字符数组名不会输出其地址,会直接输出字符
#include <iostream> using namespace std; int main() { //int a[5]={1,2,34,4,5}; //如果不是char型,cout<<"a="<<a<<endl; // 输出的为int数组首地址。不会输出数组中的值。 char a[5]="aaaa"; //cout重载了char[],可以输出整个字符串数组 cout<<"a="<<a<<endl; return 0; }
详细参考这篇博客
到此这篇关于详解c++数组和数组名问题(指针、解引用)的文章就介绍到这了,更多相关c++数组和数组名内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!