Mohican_18/3/7 C语言—回顾数组 const和define的区别

本文详细解析了数组的内存分配时机、数组与指针的区别、数组的地址操作及sizeof运算符的应用,帮助读者深入理解C语言中数组的相关概念。

关于数组的几个问题:

一.  数组什么时候分配内存?什么时候确定大小?

(这个问题理解的不是特别清楚,因为可以分情况讨论,回答参差不齐)

解析:

1.凡是声明都不占用内存。

例如:

声明一个类:

class AA
{
int a,b,c;
}
不会占内存。
typedef 
之类的声明也不会占用内存。
只在定义时占内存。比如我们定义对象:
AA a,b;

这时候就会分配内存。
(1)对全局变量,定义时就会分配内存。
(2)对局部变量,变量的内存分配在栈上进行,只有运行到变量被定义的地方时才分配内存。


    要记住的是,main()内定义的变量也是局部变量,在栈上分配内存。

举例:
FuncA()

{
  int bb[100];}

FuncB()
{
  int cc[100];
}

这两个函数中的数组都一样,仅在调用到 FUNCA FUNCB 时占用内存,当从函数中返回时

,所占用的内存将会被释放掉。如果没有调用到函数,那函数中定义的变量都不会占用内存。

二.   &arr, arr 区别

解析:

arr是数组的开始地址,但是&arr不是地址的地址,&arr也是数组的开始地址。

它们值相同,类型不同

复杂回答,牵扯到c语言的隐式转换

了解C的类型系统和隐式转换的规律首先,你要知道什么是隐式转换。

(1)C语言是存在数组类型的,对没错是数组类型,而这个类型在大多数情况下会隐式转换为它的元素的指针类型,所以你定义了一个数组int arr[10],arr的类型实际上是int[10],但是因为大部分情况下的隐式转换,arr的类型会退化为int*.大部分情况下都会发生隐式转换。

(2)不会发生隐式转换的情况有三个:

A.  sizeof运算的时候;

B. 取地址(&)运算的时候;

C. 字符串常量初始化的时候.

这也就是为什么sizeof数组是整个数组的长度,不等于sizeof指针,

在此我不想讨论C为什么要做这么多隐式转换。

上面说&运算的时候不会发生隐式转换,所以&arr,就是取arr数组的地址,取数组的地址,注意我的用词,既不是取数组名的地址,也不是取数组首元素的地址,

arr就是数组,&arr就是取数组的地址,也就是整个数组在内存中的第一个位置的地址,这个运算结果的类型是数组的指针,

也就是int(*)[10]。

arr的值在很多情况下会隐式转换为首元素地址,所以你在问题描述里就认为它就是首元素地址(但其实不是)&arr是数组的地址,碰巧和首元素地址相同但是他们类型不同.

一个是int[10]隐式转换成的int*,另一个是int(*)[10]

以上具体知识来自:

作者:孙明琦

链接:https://www.zhihu.com/question/37722442/answer/73238767

来源:知乎



三.类似问题 

int int_array[3] = {2,5,8};

printf("%p  , %p  ,  %p\n" , int_array  , &int_array[0] , &int_array);

打印的结果是0x7fff0ed1a1b0 , 0x7fff0ed1a1b0 ,0x7fff0ed1a1b0。 为什么?

解析:

(1)int_array 是个数组变量,所以它其实不是一个指针,而是栈一段长度为三个 int 的连续地址空间。

(2)但是 C 语言规定了int_array 作为值引用的时候,表示的是这段空间的首地址。

(3)&int_array C 也定义了它是合法的,取值也只能是这段连续空间的首地址(还能是什么呢?)。

(4)int_array[0],是数组 int_array 的第一个元素,它的地址当然还是这段空间的首地址。

(5)其他举例:

如果 p 是一个指针,则 p[x] 是 *(p + x),这使得 2 和 4 没有冲突。

(6)需要注意的是,如果 p 是一个指针,比如 int*,&p和 p 在类型上和数值上都是不等的。

p 是一个占用 8 或 16 个字节(可以还有别的,取决于编译器的类型)空间,这个空间中存了另一端内存的地址。

&p 是一个指针的指针(int**),指向 p 这个变量自身的地址。


部分回答详情来自:

作者:邓毅

链接:https://www.zhihu.com/question/30177303/answer/47279605

来源:知乎


四.扩展关于sizeof ()

//一维数组

int a[] = {1,2,3,4};

printf("%d\n",sizeof(a));      16//单个数组名做括号内参数代表整个数组大小

printf("%d\n",sizeof(a+0));    4//a+0表示数组的第一个元素大小

printf("%d\n",sizeof(*a));     4//*a表示数组中元素的大小

printf("%d\n",sizeof(a+1));    4//a+1表示数组的第二个元素大小

printf("%d\n",sizeof(a[1]));   4//a[1]和a+0一样表示数组的元素大小

printf("%d\n",sizeof(&a));     4//&a表示a的地址

printf("%d\n",sizeof(&a+1));   4//&a表示数组后一个元素的地址

printf("%d\n",sizeof(&a[0]));  4//&a[0]表示数组的的一个元素的地址

printf("%d\n",sizeof(&a[0]+1));4//&a[0]+1表示数组的第二个元素地址

//字符数组

char arr[] ={'a','b','c','d','e','f'};       <span style="background-color:rgb(255,102,102);">//注意和{"abcdef"}初始化的比较</span>

printf("%d\n", sizeof(arr));      6//arr表示整个数组

printf("%d\n",sizeof(arr+0));    1//arr+1表示数组的第一个元素

printf("%d\n",sizeof(*arr));     1//*arr表示数组中的元素

printf("%d\n",sizeof(arr[1]));   1//arr[1]表示数组的第二个元素

printf("%d\n",sizeof(&arr));     4//&arr表示arr的地址

printf("%d\n",sizeof(&arr+1));   4//&arr+1表示数组arr后的一个地址

printf("%d\n",sizeof(&arr[0]+1));4//&arr[1]+1表示数组的第二个元素的地址

char *p = "abcdef";

printf("%d\n", sizeof(p));        4//p表示数组地址。。。。注意“abcdef”初始化会有一个\0

printf("%d\n", sizeof(p+1));      1//p+1表示数组的第二个元素

printf("%d\n", sizeof(*p));       1//*p表示数组的首元素

printf("%d\n",sizeof(p[0]));     1//p[0]表示数组的元素大小

printf("%d\n",sizeof(&p));       4//&p表示数组的地址

printf("%d\n",sizeof(&p+1));     4//&p+1表示数组的下一个地址

printf("%d\n",sizeof(&p[0]+1));  4//&p[0]+1表示数组的第二个元素的地址

 






关于常量:

一.  define 和 const的一些区别

1.编译器处理阶段不同:

    define宏在编译预处理阶段展开, const常量修饰的只读辩论实在在编译阶段的时候确定其值。

2.类型安全检查不同

    defined宏没有类型,不做类型检查,只做简单的展开

    const常量有类型,在编译阶段会执行类型检查

3.存储方式不同

    define定义的常量在替换后运行过程中会不断地占用内存,在内存中有若干份copy,而const定义的常量存储在数据段,只有一份copy,(在静态区)效率更高。

4.能否调试

    define定义的常量不能被调试,const常量可以。

5.效率不同

编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

6.功能实质不同

        const为常量值 define为字符串的替换

=================举例说明====================

const int *p;   //p可变,p指向的对象不可变

int const*p;  //p可变,p指向的对象不可变

int *const p;  //p不可变,p指向的对象可变

const int *const p;  //指针p和p指向的对象都不可变

===============区别方法=================

 

这里有一个记忆和理解的方法:

先忽略类型名(编译器解析的时候也是忽略类型名),我们看const离哪个近。"近水楼台先得月",离谁近就修饰谁。

判断时忽略括号中的类型

====================================

const (int) *p;   //const修饰*p,*p是指针指向的对象,不可变

(int) const *p; //const修饰*p,*p是指针指向的对象,不可变

(int)*const p;   //const修饰p,p不可变,p指向的对象可变

const (int) *const p;  //前一个const修饰*p,后一个const修饰p,指针p和p指向的对象都不可变

  部分选自:https://www.nowcoder.com/questionTerminal/a60c01a7c4ab473e81218ed0b333b4e6

  网站:牛客网








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值