C语言中数组和指针的一些理解

1.c语言中,有时需要使用只读数组,也就是程序从数组中读取数值,但是程序不向数组中写数据,在这种情况下声明并初始化数组时,建议使用const。
2.变量和数组都是自动类型的,不同的存储类有时具有不同的属性,自动类型就是如果没有进行初始化,一些存储类的变量和数组会把他们的存储单元设置为0;

#include <stdio.h>
#define SIZE 4
int main(int argc, const char * argv[]) {
 int some_data[SIZE]={1492,1066};
    int i;
    printf("%s%s\n","i","some_data[i]");
    
    for (i=0; i<SIZE; i++) {
        printf("%d--%d\n",i,some_data[i]);
        
    }
return 0;
}

输出结果

isome_data[i]
0--1492
1--1066
2--0
3--0

从上面的结果我们可以看出,编译器做的很好,当数值数目小于数组元素数目时,多余的数组元素被初始化为0。也就是说,如果不初始化数组,数组元素和末初始化的普通变量一样,其中存储的是无用的数值;但是如果部分初始化数组,未初始化的元素则被设置为0;
如果初始化列表中项目的个数大于数组大小,编译器会毫不留情地认为这是一个错误。一种做法就是你可以省略括号中的数字,从而让编译器自动匹配数组大小和初始化列表中的项目数目

3.指定初始化项目
传统的我们一般会这样初始化数组:

int arr[6]={0,0,0,0,0,211};

但我们也可以在初始化列表中使用带有方括号的元素下标可以指定某个特定的元素

int arr[6]={[5]=211};

在初始化一个或多个元素后,未经初始化的元素都将被设置为0

4.指针和数组

    short dates[4];
    short*pti;
    short index;
    double bills[4];
    double*ptf;
    
    pti=dates;
    ptf=bills;
    
    for (index=0; index<4; index++) {
        printf("pointer +%d:%p %p\n",index,pti+index,ptf+index);
    }

输出结果

pointer +0:0x7fff5fbff738 0x7fff5fbff710
pointer +1:0x7fff5fbff73a 0x7fff5fbff718
pointer +2:0x7fff5fbff73c 0x7fff5fbff720
pointer +3:0x7fff5fbff73e 0x7fff5fbff728

指针的数值就是它所指向的对象的地址,地址的内部表示方式是由硬件来决定的
在指针钱运用运算符*就可以得到该指针所指向的对象的数值
对指针加1,等价于对指针的值加上它所指向的对象的字节大小

  dates+2==&dates[2];  //相同的地址
  *dates+2 == dates[2]; // 相同的值

这些关系总结了数组和指针间密切的关系,可以用指针标示数组的每个元素,并得到每个元素的数值,
顺便提一下,请注意区分(dates+2)和dates+2。间接运算符的优先级高于+,因此后者等价于:(dates)+2

*(dates+2 ) /* dates 的第3个元素的值*/
*dates+2 /*第一个元素的值和2相加*/

赋值:可以把一个地址赋给指针。通常使用数组名或地址运算符&来进行地址赋值。

 
    int urn[5]={100,200,300,400,500};
    int*ptr1,*ptr2,ptr3;
    
    ptr1=urn;
    ptr2=&urn[2];
    printf("ptr1=%p,*ptr1 =%d,&ptr1=%p\n",ptr1,*ptr1,&ptr1);
    
    //指针加法
    
    ptr3=*(ptr1+4);
    printf("ptr1+4=%p,*(ptr4+3)=%d\n",ptr1+4,*(ptr1+3));
    
    //递增指针
    ptr1++;
    printf("ptr1=%p,*ptr1=%d,&ptr1=%p\n",ptr1,*ptr1,&ptr1);
    //递减指针;
    ptr2--;
    printf("ptr2=%p,*ptr2=%d,&ptr2=%p\n",ptr2,*ptr2,&ptr2);

    --ptr1;//恢复为初始值
    ++ptr2;//恢复为初始值
    printf("ptr1=%p,ptr2=%p\n",ptr1,ptr2);
    
    //一个指针减去另一个指针
    printf("ptr2=%p,ptr1=%p,ptr2-ptr1=%ld\n",ptr2,ptr1,ptr2-ptr1);
    //一个指针减去一个整数
    printf("ptr3=%d,ptr3-2=%d\n",ptr3,ptr3-2);
   

输入结果为:

*p1=100,*p2=100,*p3=300
*p1++=100,*++p2=200,(*p3)++=300
*p1=200,*p2=200,*p3=301
ptr1=0x7fff5fbff6e0,*ptr1 =100,&ptr1=0x7fff5fbff678
ptr1+4=0x7fff5fbff6f0,*(ptr4+3)=400
ptr1=0x7fff5fbff6e4,*ptr1=200,&ptr1=0x7fff5fbff678
ptr2=0x7fff5fbff6e4,*ptr2=200,&ptr2=0x7fff5fbff670
ptr1=0x7fff5fbff6e0,ptr2=0x7fff5fbff6e8
ptr2=0x7fff5fbff6e8,ptr1=0x7fff5fbff6e0,ptr2-ptr1=2
ptr3=500,ptr3-2=498

赋值:可以把一个地址赋给指针。通常使用数组名或地址运算符&来进行地址赋值。上面例子中,把数组urn的起始地址赋给ptr1。变量ptr2得到的是数组第三个也即最后一个元素的地址。注意:地址应该和指针类型兼容,不能把一个double类型的地址赋给一个指向int的指针

求值或取值:运算符可取出指针指向地址中存储的数值。因此,ptr1开始为100,即存储在地址0x7fff5fbff6e0中的值。
取指针地址:指针变量同其他变量一样具有地址和数值,使用运算符&可以得到存储指针本身的地址。本例中,ptr1被存储在内存地址0x7fff5fbff678中,该内存单元的内容是0x7fff5fbff6e0,即urn的地址。
将一个整数加给指针:可以使用+运算符来把一个整数加给一个指针,或者把一个指针加给一个正数。两种情况下,这个正数都会和指针所指类型的字节数相乘,然后所得的结果会加到初始地址上。于是,ptr4的结果等同于&urn【4】。如果相加的结果超出了初始指针所指向的数组的范围,那么这个结果是不确定的,除非超出数组最后一个元素的地址能够确保是有效的。
增加指针的值:可以通过一般的加法或增量运算符来增加一个指针的值。对指向某数组元素指针做增量运算,可以让指针指向该数组的下一个元素。因此,ptr++运算把ptr1加上数值4,使ptr1指向urn[1]。现在ptr1的值是0x7fff5fbff6e4,*ptr的数值为200.请注意ptr1本身的地址仍然是0x7fff5fbff678。别忘了,变量不会因为它的值得变化而移动位置。
从指针中减去一个整数:可以使用-运算符来从一个指针中减去一个整数。指针必须是第一个操作数,或者是一个指向整数的指针。这个正数都会和指针所指类型的字节数相乘,然后所得到的结果会从初始地址中国减掉。于是,ptr3-2的结果等同于&urn[2].因为ptr3是指向&urn[4]的。如果相减的结果超出了初始指针所指向的数组的范围,那么这个结果是不确定的,除非超出数组最后一个元素的地址能够确保是有效的。

减小指针的值:指针当然也可以做减量运算。ptr2自减1之后,它将不再指向第三个元素,而是指向第二个数组元素。请注意,你可以使用前缀和后缀形式的增量和减量运算符。对指针ptr1和ptr2都指向同一个元素urn【1】,直到他们被重置。
5.关于const的内容
两个函数

void show_array(const double ar[],int n);
void mult_array(double ar[],int n,double mult);

double rates[5]={88.99,110.22,59.45,183.22,340.21};
const double locked[4]={0.08,0.075,0.0725,0.07};

像show_array这样的函数可以接受普通数组和常量数组的名称作为实际的参数,因为这两种参数都可以赋给指向常量的指针

show_arrary(rates,5);
show_arrary(locked,4);

但是,像mult_arrary()这样的函数不能接受常量数组的名称作为参数

mult_array(rates,5,1.2);
mult_array(locked,4,1.2);

函数参量定义中使用const,不仅可以保护数据,而且使函数可以使用声明为const的数组
关于const的用法还有很多,例如,你可以使用关键字const来声明并初始化指针,以保证指针不会指向别处,关键在于const的位置

这样的指针仍然 可以用于修改数据,但它只能指向最初赋给它的地址,不能修改所指向的地址

double * const pc1 = rates; //pc1指向数组的开始处
*pc1 = 92.99;//允许
//pc1=&rates[2]; // 不允许

指向常量的指针不能用于修改数值,但是指向的地址是可以改变的

const double locked[4]={0.08,0.075,0.0725,0.07};
const double*pc=rates; //pc指向数组开始处
//*pc=320.0; //不允许
//pc[2]=222.22;  //不允许
pc=locked;//允许
pc=&rates[4];//允许
pc++;//让pc指向rates[1]这是允许的
rates[0]=99.99; //允许 因为rates不是常量

也可以使用两个const来创建指针,这个指针既不可以更改所指向的地址,也不可以修改所指向的数据

const double* const pc2 = rates;
//    pc2 = &rates[2];//不允许
//    *pc2 = 92.99 //不允许

6.指针和多维数组
函数是通过指针来处理多维数组的
来看一个例子:
假设有如下声明:

int zippo[4][2]; 整数数组的数组

数组名zippo同时也是数组首元素的地址。在本例中,zippo的首元素本身又是包含两个int的数组,因此zippo也是包含两个int的数组的地址。下面从指针地址进一步分析:
①因为zippo是数组首元素的地址,所以zippo的值和&zippo[0]相同。另一方面,zippo[0]本身是包含两个整数的数组,因此zippo[0]的值同其首元素的地址&zippo[0][0]相同。简单地说,zippo[0]是一个整数大小对象的地址,而zippo是两个整数大小对象的地址。因为整数和两个整数组成的数组开始于同一个地址,因此zippo和zippo[0]具有相同的数值。
②对一个指针加1,会对原来的数值加上一个对应类型大小的值。在这方面,zippo和zippo[0]是不一样的,zippo所指向对象的大小是两个int,而zippo[0]所指向对象的大小是一个int,因此zippo+1和zippo[0]+1的结果不同。
③对一个指针取值得到的是该指针所指向对象的数值。因为zippo[0]是其首元素zippo[0][0]的地址,所以(zippo[0])代表存储在zippo[0][0]中的数值,即一个int数值。同样zippo代表其首元素zippo[0]的值,但是zippo[0]本事就是一个int数的地址,即&zippo[0][0],因此zippo是&zippo[0][0]。对这两个表达式同时应用取值元算符将得到zippo等价于&zippo[0][0],后者简化后即为一个int数zippozippo[0][0]。简言之,zippo是地址的地址,需要两次取值才可以得到通常的数值。地址的地址或指针的指针是双重间接的典型例子。

总结完的关系如下:

//    zippo[0] 表示首元素的地址
//    zippo[0][0] 表示元素的值
//    *zippo表示首元素的值
//    &zippo[0][0]= zippo[0]=*zippo=zippo

//    *(zippo[0])表示存储在zippo[0][0]中的数值
//    *(zippo[0])=zippo[0][0] = **zippo = *&zippo[0][0]

下面我们看一下这个输出结果

 printf("8-----*(*(zippo+2)+1)=%d\n",*(*(zippo+2)+1));

结果为:

8-----*(*(zippo+2)+1)=3

让我们来分步来建立这个表达式

zippo  //第一个大小为2个int的元素地址
zippo+2 //第3个大小为2个int的元素地址
*(zippo+2)第3个元素,即包含2个int值得数组,因此也是其第一个元素(int值)的地址
*(zippo+2)+1包含2个int值的数值的第2个元素的地址
*(*(zippo+2)+1)数组第三行第2个int(zippo[2][1])的值

7.指向多维数组的指针
如何声明一个指向二维数组的指针变量pz?例如,在编写处理像zippo这样的数组的函数时,就会用到这类指针。指向int的指针可以胜任吗?不可以。这种指针只是和zippo[0]兼容,因为它们都指向一个单个int值。但是zippo是其首元素的地址,而该首元素又是包含两个int值的数组。因此,pz必须指向一个包含两个int值的数组,而不是指向一个单个int值。下面是正确的代码:

int(*pz)[2];//pz指向一个包含2个int值的数组(使用圆括号的原因是是表达式中[]的优先级高于*)
zippo[m][n]==*(*(zippo+m)+n)
pz[m][n]=*(*(pz+m)+n)

指针之间的赋值规则比数组类型的赋值更严格。例如,你可以不需要进行类型转换就直接把一个int数值赋给一个double类型的变量。但对于指针来说,这样的赋值是不允许的
🌰

    int *pt;
    int(*pa)[3];
    int ar1[2][3];
    int ar2[3][2];
    int **p2;
    
    pt = &ar1[0][0];
    pt = ar1[0];
    pt = ar1;//非法
    pa = ar1;
    pa = ar2;//非法
    p2 = &pt;
    *p2 = ar2[0];
    p2 = ar2;//非法

8.函数和多维数组

void sum_rows(int ar[][COLS],int rows)

请注意下面的声明是不正确的

int sum2(int ar[][],int rows);//错误的声明

编译器会把数组符号转换成指针符号。这就意味着,ar[1]会被转换成ar+1。编译器这样转换的时候需要知道ar所指向对象的数据大小。

int sum2(int ar[][4],int rows);//合法声明

就表示ar指向由4个int值构成的数组,也就是16个字节长的对象,所以ar+1表示”在这个地址上加16个字节大小“。
也可以如下这样在另一对方括号中填写大小,但编译器将忽略之:

int sum2(int ar[3][4],int rows)//合法声明,但3将被忽略

一般地,声明N维数组的指针时,出了最左边的方括号可以留空之外,其他都需要填写数值

int sum4(int ar[][12][20][30],int rows);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容

  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,419评论 3 44
  • 前言 最近真的是忙的不可开交,公司一直给安排任务,连学习和写笔记的时间都没有了,落下好几次课的笔记都没有写,所以我...
    Xiho丶阅读 1,500评论 1 12
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,484评论 1 51
  • 每个函数都是Function类型的实例,而且都与其他引用类型一样具有属性和方法。函数是对象,因此函数名实际上也是一...
    royluck阅读 255评论 0 0
  • 上些天回老家,刚进门就看了新养的兔子,跟没见过几次面的一岁多的狗打了招呼,却始终没见到甚至已经忘了跟了我家好几年的...
    砍柴挑水先生阅读 153评论 1 1