第十章_数组和指针

1. 数组

  • 数组声明
int arr1[10];
float arr2[4];
char arr3[50] ;
  • 数组初始化
int arr[5] = {1,2,3,4,5};
  • 使用const声明数组,数组为只读,不能再修改。
const int arr[5] = {2,4,5,6,7};
  • 生命数组后如果未进行初始化,数组元素和未初始化的普通变量一样,存储的是垃圾值;如果只初始化了数组中的部分变量,则其余变量会被初始化为0。
  • 如果初始化数组时忽略方括号里的数字(即数组长度),编译器会根据初始化列表的项数来确定数组大小。
  • 使用for循环遍历数组时,可以通过sizeof arr / sizeof arr[0] 来获得数组元素的个数。
  • 也可以通过数组的下标来初始化数组元素
int arr[5] = {[2] = 10};    //将第三个元素初始化为10;
int arr[5] = {[1] = 3,6,8}    //将第2、3、4个元素初始化为3、6、8;
int arr[5] = {4,2,[0] = 3};    //注意,后面的初始化内容会覆盖掉前面的初始化内容
int arr[] = {[10] = 99};    //如果为指定数组长度,编译器会把数组的大小设置为足够装得下初始化的值,本数组长度为11;
  • 使用数组时要防止数组下标越界。例如长度为10的数组arr,arr[-2]或arr[10]这样的初始化会把值存在数组的前一个或后一个位置,该位置可能存储着其他变量,这些变量被覆盖掉可能导致程序运行混乱。
int a = 10;
int arr[5];
int b = 2;
//以下两条语句可能导致a和b的值被更改,编译器不会提示,所以程序员需要格外注意。
arr[-1] = 99;
arr[5] = 99;

2. 二维数组

  • 初始化二维数组,二维数组可以看做数组的数组
const int arr[3][4] = 
{
    {2,3,4,5},
    {4,5,6,7},
    {7,5,3,8}
};

3. 指针和数组

在C中,数组名就是数组首元素的地址。

int arr[5];
arr == arr[0];    //此语句成立
  • 在指针前面使用 * 运算符可以得到该指针所指向对象的值
  • 指针+1,指针的值递增他所指向类型的大小。例如arr[3]可以看做先找到内存的arr地址,然后向后移动3个元素的大小(取决于储存元素的类型),获得储存在该位置的值。
arr + 2 == &arr[2]    //两个相同的地址
*(arr + 2) == arr[2]    //两个相同的值

4. 函数、数组和指针

  • 定义一个函数时,如果该函数的参数是一个数组,形式参数应该是一个指针类型。
//函数原型可以省略参数名
int sum(int arr[]);    
int sum(int []);
int sun(int *);
int sum(int* arr);
//函数定义不能省略参数名
int sum(int arr[]){函数体}
int sum(int* arr){函数体}
  • 在函数中,使用参数列表中的数组时,要注意此时的参数并不是数组本身,它是一个指向数组首元素的指针。在使用sizeof时,得到的结果只是其中一个元素的size。
int main(){
    int arr[5] = {1,2,3,4,5};
    printf("%d",sizeof arr);    //打印的结果是20,因为arr数组有5个int值,每个int占4字节,所以整个数组的大小是20字节
    sum(arr);
}
int sum(int arr[]){
    printf("%d",sizeof arr);    //打印的结果是8,因为arr是一个指向arr首元素的指针,系统使用8字节存储地址,所以指针变量的大小是8字节(其他系统可能不是8字节)。
}
  • 函数处理数组必须知道何时开始,何时结束。可以使用一个整型参数告诉函数待处理的数组的元素个数,也可以传递两个指针,第一个指针指明数组的开始处,第二个指针指明数组的结束处。
//第一种,指明元素的个数
int sum(int arr[],int size);
int sum(int* arr,int size);
//第二种,指明数组的结束处
int sum(int arr[],int* end);
int sum(int * arr,int* end);
//下面是传入结束指针的两种用法,sum函数将参数数组的元素累加。
int sum(int arr[], int* end) {
    int sum = 0;
    //用法一
    for (size_t i = 0; i < end - arr; i++) {
        sum += arr[i];
    }
    //用法二
    while (end>arr){
        sum += *arr; //*arr表示当前数组元素的值
        arr++;  //以上两行代码也可以压缩为 sum += *arr++;
    }
  • 形式参数的指针表示法和数组表示法对于C语言来说是等价的,两个表达式都没问题。但是对于程序员来说,使用数组表示法让代码更清晰。

5. 指针操作

  • 指针赋值:注意两个指针的类型应该一致,不能把一个double类型的地址赋给指向int类型的指针。
int arr[5] = {1,2,3,4,5};
int * ptr;
ptr = &arr[4]; //把一个地址赋给指针变量
  • 解引用:使用*符号解引用指针,获得该指针指向的值。
  • 取址:使用&运算符获得指针本身的地址。
  • 指针与整数相加:整数会与指针指向类型的大小相乘,然后与指针地址相加,因此arr+4与&arr[4]等价。如果结果超出数组范围,计算结果则是未定义的(垃圾值)。除非正好超过数组末尾第一个位置,C保证该指针有效
  • 递增指针:递增指向数组的指针可以让该指针指向数组的下一个元素
  • 指针减去一个整数:与指针与整数相加相反。&arr[3]-2与arr[1]等价。
  • 递减指针:与递增相反,让指针指向前一个元素
  • 指针求差:两个指针指向同一个数组的不同元素,通过求差得出两个元素的距离。例如int数组中,pointer1 - pointer2得4,指的是两个指针相隔4个int。
    注意,编译器不会检查指针是否仍指向数组元素

创建一个指针时,系统只分配了储存指针本身的内存,没有分配存储数据的内存。因此,在使用指针之前,必须先用已分配内存的地址初始化它。

double * pointer; //未初始化的指针
*pointer = 2.5;   //严重的错误

double d = 6;
pointer = &d;    //正确的初始化

6. 保护数组中的数据

在函数中,对参数列表的数组进行操作会修改原数组的值,因为函数通过指针直接使用了原始数据。如果函数的意图不是修改原数组的数据内容,在函数原型和函数定义中应该使用const关键字,避免误操作修改数组内容。这样如果遇到修改数组内容的表达式,编译器会报出错误。

int sum(const int arr[]);
int sum(const int arr[]){函数体}

只能把非const数据的地址赋给普通指针。使用非const标识符修改const数据,结果是未定义的。

double rates[5] = {1.1,2.3,442.1,44.2,54.1};
double * const pointer = rates;
pointer = &rates[2] ;    //不允许,该指针不能指向别处
*pointer = 45.55;    //允许,可以改变rates[0]处的值
const double * const ptr = rates; //既不允许该指针指向别处,也不允许改变地址上的值。
ptr = &rates[4];  //不允许
*ptr = 45.545;    //不允许

7. 指针和多维数组

    int arr[4][2] = {{5, 6}, {8, 4}, {3, 2}, {9, 7}};
    printf("arr[0] = %d",**arr);   //取得的是5,等价于*arr[0]
    printf("*arr[2] = %d\n",*arr[2]);  //取得的是3

要特别注意,与 zippo[2][1]等价的指针表示法是((zippo+2) + 1)。看上去比较复杂,应最好能理解。下面列出了理解该表达式的思路:

int n = 5;
double d = 10.1;
int * ptr1 = &n;
double * ptr2 = &d;
d = n;    //可以,隐式类型转换
ptr2 = ptr1;    //不允许两个不同类型的指针赋值

//假设有如下声明
int * pt;
int (*pa)[3];  //一个指向包含三个int元素数组的指针
int ar1[2][3];
int ar2[3][2];
int **p2; // 一个指向指针的指针
有如下的语句:
pt = &ar1[0][0]; // 都是指向int的指针
pt = ar1[0]; // 都是指向int的指针
pt = ar1; // 无效
pa = ar1; // 都是指向内含3个int类型元素数组的指针pa = ar2; // 无效
p2 = &pt; // both pointer-to-int *
*p2 = ar2[0]; // 都是指向int的指针
p2 = ar2; // 无效

在函数原型中声明多维数组时,第一个方括号中的值省略(即使填写,编译器也会忽略该值)。后面的方括号的数值不能省略。

int sum(int arr [] [10] [5],int rows );  //正确,等价于下面的语句
int sum(int (*arr)[10][5],int rows);

变长数组:变长数组不能改变大小,这里的变指的是在创建数组时,可以使用变量制定数组的维度。

int sum2d(int rows,int cols,int arr[rows][cols]); //ar是一个变长数组,注意不能改变参数顺序
//或者使用下面的格式
int sum2d(int ,int ,int arr[*][*]); 
//函数定义如下,该函数可以处理任意大小的二维int数组
int sum2d(int rows, int cols, int ar[rows][cols]) {
    int r;
    int c;
    int tot = 0;
    for (r = 0; r < rows; r++) {
        for (c = 0; c < cols; c++) {
            tot += ar[r][c];
        }
    }
    return tot;
}

8. 复合字面量

复合字面量是提供临时需要的值的一种手段。一旦离开定义该字面量的块作用域,程序无法保证该字面量是否存在。

//普通创建数组
int arr[2] = {1,2};
//使用复合字面量创建匿名数组
(int [2]){1,2};
(int []){1,2,3};  //也可以省略大小,编译器会自动计算数组元素个数
//因为复合字面量是匿名的,所以不能先创建再使用它,必须在创建的同时使用。
int *ptr;
ptr = (int [ ]){1,2,3};
//也可以使用复合字面量作为实际参数传给函数;
int sum(int * p,int size);    //函数原型
sum((int []){2,3,4},3);    //这样的好处是使用函数时不用先创建数组
int (*ptr)[4];    //声明一个指针
ptr = (int [2][4]){{12,3,44,5},{3,4,5,6}};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,505评论 1 51
  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,423评论 3 44
  • 按月打印天数 编译器 计算元素的 个数 #include #include #define MONTHS 12 v...
    小风xf阅读 484评论 0 0
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,084评论 1 32
  • 这是C++类重新复习学习笔记的第 四 篇,同专题的其他文章可以移步:https://www.jianshu.com...
    超级超级小天才阅读 485评论 0 1