详解C++数组和数组名问题(指针、解引用)

 更新时间:2021年09月18日 12:06:49   作者:IE猫  
这篇文章主要介绍了详解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]

整理:

指针 类型iaint (*) [4]&1aint (*) [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个字节)

aa + 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++数组和数组名内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解计数排序算法及C语言程序中的实现

    详解计数排序算法及C语言程序中的实现

    技术排序算法与我们普通接触的冒泡排序和快速排序等基于元素比较的算法不同,在编程中通过C语言的数组能够清除地表达出来,这里我们就来详解计数排序算法及C语言程序中的实现
    2016-07-07
  • 浅谈使用Rapidxml 库遇到的问题和分析过程(分享)

    浅谈使用Rapidxml 库遇到的问题和分析过程(分享)

    下面小编就为大家带来一篇浅谈使用Rapidxml 库遇到的问题和分析过程(分享)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • C++中指针和引用的区别详解

    C++中指针和引用的区别详解

    这篇文章主要介绍了C++中指针和引用的区别详解的相关资料,需要的朋友可以参考下
    2017-02-02
  • C++中const用法小结

    C++中const用法小结

    C++ const 允许指定一个语义约束,编译器会强制实施这个约束,允许程序员告诉编译器某值是保持不变的。如果在编程中确实有某个值保持不变,就应该明确使用const,这样可以获得编译器的帮助。
    2016-04-04
  • C语言结构体struct详解

    C语言结构体struct详解

    C语言中,结构体类型属于一种构造类型(其他的构造类型还有:数组类型,联合类型),下面这篇文章主要给大家介绍了关于C语言结构体(struct)的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2022-09-09
  • C++ template用法案例详解

    C++ template用法案例详解

    这篇文章主要介绍了C++ template用法案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-09-09
  • Qt中CQGUI框架之阴影圆角窗口实现

    Qt中CQGUI框架之阴影圆角窗口实现

    这篇文章主要介绍了Qt中CQGUI框架之阴影圆角窗口实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • C语言中getchar()与putchar()函数详解

    C语言中getchar()与putchar()函数详解

    本文主要介绍了C语言中getchar()与putchar()函数详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01
  • C++踩坑实战之构造和析构函数

    C++踩坑实战之构造和析构函数

    不论是构造函数,还是析构函数,都是C++、C#语言相对于其他语言而言特殊的地方,它是为了方便类中对象的初始化,这篇文章主要给大家介绍了关于C++踩坑实战之构造和析构函数的相关资料,需要的朋友可以参考下
    2021-07-07
  • C语言的动态内存管理的深入了解

    C语言的动态内存管理的深入了解

    这篇文章主要为大家详细介绍了语言C的动态内存管理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-02-02

最新评论