数组与指针

一、数组的初始化

  1、当初始化列表中的值少于数组元素个数时,编译器会把剩余的元素都初始化为0;

  2、如果初始化数组时省略方括号中的数字,编译器会根据初始化列表中的项数来确定数组的大小;

  3、对于数组中个数的多少,有一个比较不容易出错的方法 - sizeof () 的使用;

  e.g. : int nums[] = {31, 28, 31, 30};

      int count = sizeof nums / sizeof nums[0]; //计算数组中的元素个数

  【sizeof ():给出运算对象的大小,以字节为单位。在上述的语句中,sizeof nums 是整个数组的大小,sizeof nums[0] 是数组中一个元素的大小。】

  4、C99 中可以初始化指定的数组元素。

int arr[7] = {1, 2, [4] = 23, 8, [1] = 12}

  上述语句的结果为:arr[0] = 1, arr[1] = 12, arr[2] = 0, arr[3] = 0, arr[4] = 23, arr[5] = 8, arr[6] = 0。

   5、C 不允许把数组作为一个单元赋给另外一个数组,除初始化以外也不允许使用花括号列表的形式进行赋值。  

#define SIZE 5
int main (void)
{
    int oxen[SIZE] = {5, 3, 2, 8};
    int yaks[SIZE] ;
    
    yaks = oxen; /*不允许*/
    yaks[SIZE] = oxen[SIZE]; /*数组下标越界*/
    yaks[SIZE] = {5, 3, 2, 8}; /*不起作用*/          
}   

二、多维数组的理解 

  1、二维数组的理解

float rain[5][12];    //内含5个数组元素的数组,每个数组元素内含12个float类型的元素

  换句话说,rain中每个元素本身都是一个内含12个float 类型值的数组。

  2、三维数组的理解

int box[10][20][30];

  对于box,其理解方式有:1,box内含10个元素,每个元素是内含20个元素的数组,这20个数组元素中的每个元素是内含30个元素的数组;2,把box想像成有10个二维数组(每个二维数组都是20行30列)堆叠起来。

  3、多维数组的理解

  与1、2的内容相类似。

三、指针 & 数组

  1、指针的值是其指向对象的地址。地址的表示方式依赖于计算机内部的硬件。

  2、指针前使用 ‘ * ’ 运算符可以得到该指针所指向对象的值。

  3、指针加1,指针的值递增其所指向类型的大小(以字节为单位)。

四、函数 & 数组 & 指针

  1、声明数组形参

  对于函数原型中的数组形参的声明,下面的几种原型是等价的。

int sum (int *ar, int n);
int sum (int *, int n);
int sum (int ar[], int n);
int sum (int [], int n);

  但是,在函数定义的时候不能省略参数名,下面的几种函数定义是等价的。

int sum (int *ar, int n){
    //函数主体
}

int sum (int ar[], int n){
    //函数主体
}

  2、‘ * ’运算符与‘ ++ ’ / ' -- '运算符的结合

total += *star++; //先‘递增’后指向
total += (*star)++; //先指向后递增

  对于上述语句,‘ * ’运算符与‘ ++ ’运算符的优先级相同,但是结合律是从右到左,所以先计算start++,之后再计算*start,先‘递增’后指向。使用后缀形式(start++,而不是++start)意味着先把指针指向位置上的值加到total上,然后再递增指针;如果使用前缀形式(++start,而不是start++),意味着要先递增指针,之后把指针指向位置上的值加到total上。

五、指针的几种操作

int urn[5] = { 100, 200, 300, 400, 500 }
int * ptr1, *ptr2, *ptr3;

  1、赋值

ptr1 = urn;    //将一个地址赋给指针
ptr2 = &urn[2];    //将一个地址赋给指针

  2、解引用

printf("ptr1 = %p, *ptr1 = %d, &ptr1 = %p
", ptr1, *ptr1, &ptr1);

  解引用 - “ *ptr1”。给出指针指向的地址上存储的值。

  3、取址

printf("ptr1 = %p, *ptr1 = %d, &ptr1 = %p
", ptr1, *ptr1, &ptr1);

  取址 - “ &ptr1”。指针也有自己的地址,‘ & ’运算符给出的是指针本身的地址。

  4、指针与整数相加

ptr3 = ptr1 + 4;    //指针加法

  ' ptr1 + 4 ' 等价于 &urn[4] 。

  5、递增指针

ptr1++;    //指针递增

  ‘ ptr1++ ’ 等价于ptr1的值加上4(假设系统中 int 数据为4字节),pte1 指向 urn[1] 。

  6、指针减去一个整数

printf("ptr3 - 2 = %p
", ptr3 - 2);

  整数将乘以指针指向类型的大小(以字节为单位),然后用初始地址减去乘积。

  7、递减指针

ptr2--;    //指针递减

  8、指针求差

printf("ptr2 - ptr1 = %td
", ptr2 - ptr1);

  通常两个指针指向同一个数组的不同元素,通过计算求出两元素之间的距离。差值的单位与数组类型的单位相同。如果 ptr2 - ptr1 = 2,则意味着两个指针所指向的元素之间相差着两个 int ,而并不是两个字节。

  9、比较

  使用关系运算符进行比较,前提是两者都是指向相同类型的对象。

六、 const 修饰符的使用 

  1、对形参使用 const

int sum (const int ar[], int n);  /*函数原型*/

int sum (const int ar[], int n){
    //函数体
}

  上述的 const 修饰符告知编译器:sum 函数不可以修改 ar 指向的数组中的内容。如果在 sum 函数中使用的类似于 ar[i]++的表达式,编译器会很容易地检测到错误。

  这里只用的 const 修饰符并不是要求原数组是常量,而是该函数在处理数组时将其看做是常量,不可更改。

  一般而言,如果函数需要修改数组,在声明数组形参时不使用 const 修饰符;如果编写的函数不用修改数组,在声明数组形参时使用 const 修饰符。

   2、const 修饰符的其他内容

  【 const 数组】

#define SIZE 3
...
const int data[SIZE] = {2, 4, 7};
...
data[2] = 5;    //编译错误

  【 const 指针】 

double index[3] = {1.1, 2.3, 4.5};
const double * pd = index;
...
*pd = 3.8;  //不允许
pd[2] = 9.0;  //不允许
index[2] = 9.0; //允许,index 未被限定

  需要注意的是,可以让 pd 指向别处:' pd ++ ;' 是允许的。

  【其他】

  把 const 数据或非 const 数据的地址初始化为指向 const 的指针或为其赋值是合法的。

double rates [4] = {88.99, 100.12, 4.98, 76.45};
const double locked[3] = {1.2, 3.5, 7.9};
const double * pc = rates;    //有效
pc = locked;    //有效
pc = &rates[2];    //有效

  只能把非 const 数据的地址赋给普通指针。

double rates [4] = {88.99, 100.12, 4.98, 76.45};
const double locked[3] = {1.2, 3.5, 7.9};
double * pc = rates;    //有效
pc = locked;    //无效
pc = &rates[2];    //有效

  C 标准规定,使用非 const 修饰符修改 const 数据,导致的结果是未定义的。【不应该把 const 数组名作为实参传递给相应形参为非 const 数组名的函数。】

   可以声明并初始化一个不能指向别处的指针,关键是 const 修饰符的位置。

double rates [4] = {88.99, 100.12, 4.98, 76.45};
double * const pc = rates;    //pc 指向数组的开始
pc = &rates[1];    //无效,pc 不允许指向别处
*pc = 99.9;    //有效,更改 rates[0] 的值

  可以使用 const 修饰符两次,使得指针即不能更改其所指向的地址,也不能更改其指向的地址上的值。

double rates [4] = {88.99, 100.12, 4.98, 76.45};
const double * const pc = rates;    //pc 指向数组的开始
pc = &rates[1];    //无效,pc 不允许指向别处
*pc = 99.9;    //无效,不允许更改 rates[0] 的值

 七、指针与多维数组

  1、简单示例

int zippo[4][2];
/*
zippo  --  二维数组首元素的地址(每个元素都是内含两个 int 类型元素的一维数组)
zippo + 2 -- 二维数组的第3个元素(即一维数组)的地址
*(zippo + 2) -- 二维数组的第3个元素(即一维数组)的首元素(一个 int 类型的值)的地址
*(zippo + 2) + 1 -- 二维数组的第3个元素(即一维数组)的第2个元素(一个 int 类型的值)的地址
*(*(zippo + 2) + 1) -- 二维数组的第3个一维数组的第2个 int 类型的值,也就是 zippo[2][1]
*/

  2、相关的指针数组

int (* pz) [2];  //pz指向一个内含两个 int 类型值的数组
int * pzx[2];  //pax是一个内含两个指针元素的数组,每个元素都指向 int 的指针

  3、指针的兼容性

  【两个不同类型的指针之间不可以相互赋值

  4、需要处理二维数组的函数中的形参的声明

...
#define ROWS 3
#defien COLS 4
...
void sum_rows (int ar[][COLS], int rows);  //空的方括号表明 ar 是一个指针
void sum_rows (int (*ar) [COLS], int rows);
void sum_cols (int [][COLS], int );  //可以,省略形参名
int sum2d(int (*ar) [COLS], int rows);  //另一种语法

八、复合字面量【C99 新增

  字面量:除符号常量之外的常量。

  1、示例

  【pre】5 -- int 类型字面量;2.3 -- double 类型字面量;'Y' -- char 类型字面量;"elephant" -- 字符串字面量。

  【新增 - 数组】对于数组,复合字面量类似于数组初始化列表,前面是用括号括起来的类型名。

double diva [2] = {2, 4};    //普通的数组声明
(int [2]) {2, 4};    //复合字面量

  【去掉声明中的数组名,留下的 int [2] 即是复合字面量的类型名。】

  2、相关用法

  【与有数组名的数组初始化一样,同样可以省略数组大小,编译器会自动计算数组当前的元素个数。】

(int []) {10, 20, 40}    //内含3个元素的复合字面量

  【复合字面量是匿名的,必须在创建的同时去使用。常见的用法是指针记录地址。】

int *pt1;
pt1 = (int [2]) {10, 20};    //复合字面量的类型名也代表着首元素的地址,*pt1 = 10,pt[1] = 20

  【可以把复合字面量作为实际参数传递给带有匹配形式参数的函数。】

int sum (const int ar[], int n);
...
int toal3;
total3 = sum((int []) {4, 4, 4, 5, 5, 5}, 6);//第一个实参:内含6个 int 类型值的数组。把信息传入函数前不必先创建数组,此为复合字面量的典型用法
原文地址:https://www.cnblogs.com/wyt123/p/11005999.html