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

目录
  • 一、指针
    • 1.1 指针变量和普通变量的区别
    • 1.2 为什么需要指针
    • 1.3 指针使用三部曲
  • 二、整形、浮点型数组
    • 2.1 数组名其实是特殊的指针
    • 2.2 理解复杂的数组的声明
    • 2.3 数组名a、数组名取地址&a、数组首元素地址&a[0]、指向数组首元素的指针*p
    • 2.4 对数组名以及取值符&的理解
  • 三、字符数组数组名

一、指针

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

(0)

相关推荐

  • c++将数组名作为函数参数对数组元素进行相应的运算

    用数组名做函数参数与用数组元素作实参有几点不同: (1)用数组元素作实参时,只要数组类型和函数的形参变量的类型一致,那么作为下标变量的数组元素的类型也和函数形参变量的类型是一致的.因此,并不要求函数的形参也是下标变量.换句话说,对数组元素的处理是按普通变量对待的.用数组名作函数参数时,则要求形参和相应的实参都必须是类型相同的数组,都必须有明确的数组说明.当形参和实参两者类型不一致时,将会发生错误. (2)用普通变量或下标变量作函数参数时,形参变量和实参变量都是由编译系统分配的两个不同的内存单元.

  • C++指针数组、数组指针、数组名及二维数组技巧汇总

    本文较为详细的分析了关于理解C++指针数组,数组指针,数组名,二维数组的一些技巧.是比较重要的概念,相信对于大家的C++程序设计有一定的帮助作用. 一.关于数组名 假设有数组: int a[3] = {1, 2, 3} 1.数组名代表数组第一个元素的地址,注意,不是数组地址(虽然值相等),是数组第一个元素地址,a 等同于 &a[0]; a+1是第二个元素的地址.比第一个元素地址a(或者&a[0])超出了一个整型指针的大小,在这里是4个字节(byte) cout << a <

  • 详解C++编程中用数组名作函数参数的方法

    C++数组的概念 概括地说:数组是有序数据的集合.要寻找一个数组中的某一个元素必须给出两个要素,即数组名和下标.数组名和下标惟一地标识一个数组中的一个元素. 数组是有类型属性的.同一数组中的每一个元素都必须属于同一数据类型.一个数组在内存中占一片连续的存储单元.如果有一个整型数组a,假设数组的起始地址为2000,则该数组在内存中的存储情况如图所示. 引入数组就不需要在程序中定义大量的变量,大大减少程序中变量的数量,使程序精炼,而且数组含义清楚,使用方便,明确地反映了数据间的联系.许多好的算法都与

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

    目录 一.指针 1.1 指针变量和普通变量的区别 1.2 为什么需要指针 1.3 指针使用三部曲 二.整形.浮点型数组 2.1 数组名其实是特殊的指针 2.2 理解复杂的数组的声明 2.3 数组名a.数组名取地址&a.数组首元素地址&a[0].指向数组首元素的指针*p 2.4 对数组名以及取值符&的理解 三.字符数组数组名 一.指针 1.1 指针变量和普通变量的区别 指针:指针的实质就是个变量,它跟普通变量没有任何本质区别.指针完整的应该叫指针变量,简称为指针. 是指向的意思.指针

  • 详解Spring注入集合(数组、List、Map、Set)类型属性

    注入集合(数组.List.Map.Set)类型属性 (1)创建类,定义数组,list,map,set类型属性,并且生成对应的set方法. (2)在spring配置文件中进行配置. Stu类: package com.Keafmd.spring5.collectiontype; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; /** * Keafmd * * @C

  • C语言 详解如何删除有序数组中的重复项

    目录 删除有序数组中的重复项Ⅰ a.思路 b.图解 c.代码 d.思考 删除有序数组中的重复项Ⅱ a.思路 b.图解 c.代码 d.思考 删除有序数组中的重复项Ⅰ a.思路 定义变量 int dest=0,cur=1,nums[cur]与nums[dest]逐一比较. nums[cur]!=nums[dest],将nums[cur]放入dest下一个位置,更新dest. nums[cur]!=nums[dest],cur移动. cur==numsSize,结束.返回dest+1. b.图解 c.

  • C++详解如何实现动态数组

    目录 动态数组 示例代码 运行环境 运行效果 动态数组 动态数组Vector可以动态扩展内存,其采用连续的内存空间,当内存空间不足,便以原来的容量的2倍或者1.5倍成倍的扩展,将原有的数组元素拷贝到新分配的内存空间中,释放原有的内存空间,新的元素将存入的新分配的内存空间. 示例代码 动态数组vector的size函数和capacity函数,分别作为求数组中现有的元素的个数和数组所能容纳的元素的个数.下面直接上实现的代码. DynamicArray .h #pragma once class Dy

  • 详解Go语言中数组,切片和映射的使用

    目录 1.Arrays (数组) 2.切片 2.1 make创建切片 3.映射Map Arrays (数组), Slices (切片) 和 Maps (映射) 是常见的一类数据结构 1.Arrays (数组) 数组是定长的. 长度不可改变. 初始化 package main import ( "fmt" ) func main() { var scores [10]int scores[0] = 99 fmt.Printf("scoers:%d\n", scores

  • 详解Vue如何监测数组的变化

    目录 一.使用 Vue.js 提供的方法来更新数组 二.使用专门用于监测数组变化的语法糖 三.使用Vue.observable()函数 四.使用 computed 属性和 watch 属性监测数组变化 五.使用 Deep Watcher 方法 六.使用 $watch 函数 七.使用 Vue 的 $forceUpdate() 方法 八.使用 Vue 中的 $nextTick() 方法 九.使用 reactive 函数 十.使用 vue-devtools 中的 track 功能 在 Vue 中,如果

  • 详解Java中的数组与字符串相关知识

    Java数组的定义和使用 如果希望保存一组有相同类型的数据,可以使用数组. 数组的定义和内存分配 Java 中定义数组的语法有两种: type arrayName[]; type[] arrayName; type 为Java中的任意数据类型,包括基本类型和组合类型,arrayName为数组名,必须是一个合法的标识符,[ ] 指明该变量是一个数组类型变量.例如: int demoArray[]; int[] demoArray; 这两种形式没有区别,使用效果完全一样,读者可根据自己的编程习惯选择

  • 详解C++编程中数组的基本用法

    可以使用数组下标操作符 ([ ]) 访问数组的各个元素. 如果在无下标表达式中使用一维数组,组名计算为指向该数组中的第一个元素的指针. // using_arrays.cpp int main() { char chArray[10]; char *pch = chArray; // Evaluates to a pointer to the first element. char ch = chArray[0]; // Evaluates to the value of the first e

  • C语言中0数组\柔性数组的使用详解

    前言: 上次看到一篇面试分享,里面有个朋友说,面试官问了char[0] 相关问题,但是自己没有遇到过,就绕过了这个问题. 我自己在这篇文章下面做了一些回复. 现在我想结合我自己的理解,解释一下这个 char[0] C语言柔性数组的问题. 0数组和柔性数组的介绍 0数组顾名思义,就是数组长度定义为0,我们一般知道数组长度定义至少为1才会给它分配实际的空间,而定义了0的数组是没有任何空间,但是如果像上面的结构体一样在最后一个成员定义为零数组,虽然零数组没有分配的空间,但是它可以当作一个偏移量,因为数

随机推荐