您的位置:首页 > 其它

VS_C_17/12/12 C中数组与宏和const以及typedef的总结

2018-01-26 03:15 323 查看
4000

在总结数组与const等区别之前,可以先给一个意见,把我在写这篇博客之前看的几本书介绍一下,对学习C基础很有帮助:

1,《C程序设计语言第2版》

2,《C程序设计第4版.谭浩强》 经典老书

3,《C语言深度剖析》

4,《高质量C/C++》

5,《C和指针》

一:数组

我们先给出一些概念:

一维数组:

1.1int str[] ;这样我们定义一个数组,注意它在定义时在内存中是分配

一块内存,即所分配的这一块内存都属于str这个数组所有,就算数组中的

不够填满这块内存,也会在后面补0.

e.g: int str[10] = {1,2,3,4,5};在内存中显示为{1,2,3,4,5,0,0,0,0,0}

1.2我们也可以在初始化数组后不定义数组的长度,编译器可以自动算

出数组的长度:

e.g:int str[] = {1,2,3,4,5};这样定义也是对的。

1.3访问数组数可以用数组名加下标的方式。注意,下标是从0开始的,且

C中并不检查数组下标是否越界,所以在访问数组时需注意边界问题。//烫烫烫~

1.4若定义一个数组:int str[];那 *str则表示数组的第一个元素。

str即数组名即数组中第一个元素的地址。

1.5一维数组的元素作为函数实参,与同类型的简单变量作为实参一样,

是单向的值传递,即数组元素的值传给形参,从而是改变不影响实参。

但注意:数组名代表着整个数组的地址,如果一维数组的名字作为函数

实参,传递的是整个数组,即形参数组和实参数组完全相同,是存放在同一

储存空间的同一个数组,这样形参数组修改时,实参数组也同时被修改了.

形参数组的个数可以省略。

E.g(形参与实参):

#include <stdio.h>
#include <stdlib.h>
void Lbf1(int str[],int a,int b)//传入数组形参和变量形参
{
int tmp;
str[0] = 2;                 //修改str[0]的值为2
tmp = a;                    //交换a,b的值
b = tmp;
a = b;
}
int main()
{
int str[] = { 1, 2, 3, 4, 5 };//定义数组实参
int a = 10;                   //定义变量实参
int b = 20;
Lbf1(str, a,b);
printf("%d\n",str[0]);        //打印数组str[0]
printf("%d  %d\n", a, b);      //打印变量
system("pause");
return 0;
}


最后打印时>>>>>>a = 10 b = 20 str[0] = 2

说明在被调函数中修改普通的变量形参是不会改变main函数中的实参值的,而修改被调函数中数组的元素则会改变main函数中的实参值。

对于一维数组。我们估计都很熟悉了,但你知道下面的问题吗?

#include <stdio.h>
int main()
{
int arr[8] = { 0,1,2,3,4,5,6,7,8 };//假设数组首元素的首地址arr = 100;
int *p = arr + 3;
printf("%d %d", arr + 1,p + 1);
printf("%d %d", *(arr + 1),*(p + 1));
printf("%d %d", &arr,&p);
printf("%d &d", *arr + 1,*p + 1);
printf("%d %d", arr[-2],p[-2]);
printf("%d %d", &arr[-2],&p[-2]);
return 0;
}


关于arr的打印 (建议打开运算符优先级表)

1, arr + 1 是数组首元素的首地址进行操作而我们知道在C中数组的数组名在许多地方已经退化为一个指向数组首元素的首地址的指针,所以这是待对指针进行操作,指针+1 在数组中代表移动一个单元格,这个数组为int类型所以移动了4个字节 其结果为 104

2, *(arr + 1)括号的优先级高,所以是在上面对指针的操作+1 之后对其进行解引用。104地址所对应是元素 1。

3, &arr 我们应该知道这是对数组首元素的首地址 取地址,所以就是 100了。

4, *arr + 1 , 星号的优先级高,是对数组首元素的首地址解引用之后 +1 的操作,所以就是 0 + 1 = 1。

5, arr[-2] 首先arr数组中只有从下标 0 - 7的8个元素。所以这种操作是非法的,所以会打印乱码。

6, &arr[-2] 跟上面一样,这个元素都不存在,也就没有地址可言

关于p的打印

1,p + 1 跟上面一样是对指针的操作,但需要注意的是 p 指向的是arr + 3即指向了数组的第一个元素的地址+3 的元素 也就是第4号下标的元素 再加 1 就是4号下标的地址 所以是 。100 + 4*4 = 116

2, *(p + 1) 由上面可以知道 解引用后的值应该是数组的第5个元素 4

3,&p 乍一看是不是像是打印第4个元素的地址,然后高兴的写上112,但那是错的,因为我们知道,在定义一个指针指向一个变量或者一个数组名时,是将该变量的地址或者数组首元素的首地址的指赋给了p,但p本身也是一个变量也有自己的地址空间,但这个空间是由操作系统分配的,与数组的地址空间无任何联系,我们无法通过arr数组的地址来算出p的地址,所以这个答案可以填 未知。

4,p[-2] 上面我们说道arr[-2] 数组访问越界了,arr中并没有这个元素,但看这里,p 指向的是arr数组的第4个元素,所以对p取-2 的元素是合法的,相当于指针回退两个单元格,也就是 1

5,&p[-2] 那这个就显而易见了,就是对 1 取地址,也就是100 + 4 = 104

二维数组:

对于二维数组,我们依然给出一些定义:

1.编译器总是将二维数组看成是一个一维数组,而一维数组的每一个元素又都是一个一维数组。

二维数组在内存中依然是线性存储,第一行元素与第二行元素无间隔。

2.C 语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。这条规则并不是递归的,也就是说只有一维数组才是如此,当数组超过一维时,将第一维改写为指向数组首元素首地址的指针之后,后面的维再也不可改写语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。这条规则并不是递归的,也就是说只有一维数组才是如此,当数组超过一维时,将第一维改写为指向数组首元素首地址的指针之后,后面的维再也不可改写。比如:a[3][4][5]作为参数时可以被改写为(*p)[4][5]。

扩充:一个二维数组可以看成一个一维数组,里面的每个元素又分别代表一个一维数组。同样的三维数组也可以这样看成一个二维数组,但是却不能看成一个一维数组,即只能向下降一维解释。

看完了定义我们来看一些题型:

#include <stdio.h>
int main()
{
//以下定义合法吗?
int arr[3][4] = { { 0, 1, 2, 3 }, { 4, 5, 6, 7 }, { 8, 9, 10, 11 } };
int brr[3][4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
int crr[][4] = { { 0, 1, 2, 3 }, { 4, 5, 6, 7 }, { 8, 9, 10, 11 } };
int drr[3][] = { { 0, 1, 2, 3 }, { 4, 5, 6, 7 }, { 8, 9, 10, 11 } };
int err[][] = { { 0, 1, 2, 3 }, { 4, 5, 6, 7 }, { 8, 9, 10, 11 } };

//假定数组首元素的首地址arr的地址为100
printf("%d %d\n",arr[0][4],arr[1][0]);
printf("%d\n", &arr + 1);
printf("%d\n",arr + 1);
printf("%d\n", *(arr + 1));
return 0;
}


我们先看上面的定义

1,int arr[3][4] 肯定是合法的,这是最标准的定义方法。它后面的赋值也是最标准的二维数组赋值方法。

2,int brr[3][4] 是标准定义,其后的赋值虽不符合标准定义但也是合法的,可以看上面的给出的定义,编译器总是将二维数组看成是一个一维数组,而一维数组的每一个元素又都是一个一维数组。二维数组在内存中依然是线性存储,第一行元素与第二行元素无间隔。所以完全可以按照一维数组的赋值方式赋值,只不过得提前算好赋值的元素个数。

3,int[][4] 首先这也是合法的,这是一种非标准的定义,所以这里这可以给出【规定】在定义二维数组时,除标准形式之外,也可以只给出列数。其后的赋值跟第二种一样,是合法的。

4,int[3][]与前一个形成对比,很显然是错误的定义。

5,int[][] 不解释看第二条的规定,所以也是不合法的。

再看下面的打印

1,arr[0][4]与arr[1][0] 可以参照上面的给出的概念的第一条,编译器总是将二维数组看成是一个一维数组,所以arr[0][4] 其实就是第0行的第4个元素 也就是第1行的第0个元素,所以其值应该等于arr[1][0]为4

2,&arr + 1 跟上面的一维数组一样 取地址 + 1 是加了整个数组的长度。

3,arr + 1 虽然也是对指针的操作,但跟上面的一维数组有点不同,但我们可以将这个二维数组看成一个一维数组arr[3],
d0a1
里面的每一个元素都是由4个元素组成的一个集合,那么我们对首地址arr这个伪指针进行 +1 操作是,就是加了一个(单元格)集合的长度,即4*4=16个字节,所以这个答案为:116

4,*(arr + 1) 由上面可得,这是对116地址进行解引用,即第1行的第0号下标的值(第5个值):4

在看完上面的一维数组及二维数组的总结之后,我们来做一道题练一练手:

#include<stdio.h>
#pragma pack(4)
struct S
{
char a;
double v;
int d;
short x;
};
int main()
{
int arr[][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
double *brr[5][4] = { 0 };
struct S* srr[3][2];
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(brr));
printf("%d\n", sizeof(srr));
return 0;
}


我们首先看看答案 : 分别打印的是 48 ,80,24。

是不是有点不一样。我们来分析一下:

1,对于第一个printf 定义了一个二维数组arr[][4] 在上面我们说过这种定义方式是没问题的,但是并没有说应该注意什么。这里就挖了一个坑。

【规则】在定义二维数组时,如果只定义了列数,那么编译器会自动算出行长,然后得出数组的长度。

但这个计算是根据当前赋值的元素及列数计算出来的,在arr[][4] 中有1 - 10,10个元素,列长为4,那编译器算出的第一行就为{1,2,3,4} 第二行为{5,6,7,8} 第三行为{9,10,0,0}注意最后一行因为不够4个根据数组赋值规则自动补 0 了。所以arr数组的实际在内存中的长度为12个int类型的数据,即12 * 4 = 48。

2,对于第二个和第三个其实可以统一说明:

【规则】在C中不管是定义的何种类型的指针在内存中所占字节数都为4个字节。在这两个定义中都是定义了一个指针数组(数组里面存放的是指针),所以元素都只占4个字节

第二个是 5*4*4 = 80,第三个是3*2*4 = 24

二:define 与 const 和 typedef的区别

2.1存储方式不同

  define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内 存。(宏定义不分配内存,变量定义分配内存。)

  typedef只是定义别名

2.2类型和安全检查不同

  define宏没有类型,不做任何类型检查,仅仅是展开。

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

2.3编译器处理方式不同

  define宏是在预处理阶段展开。

  const常量是编译运行阶段使用。

  typedef是在编译运行阶段处理的。

2.4功能差异

define宏只能做简单的字符串替换。

const是为了保证某些重要变量不可被修改。

typedef给某种类型(例如unsigned long long int)取一个别名。

E.g(const):

#include <stdio.h>
int main()
{
const int a = 10;
int *p;
const int *ps;
a = 20;//error表达式必须是可修改的左值
p = &a;//error不能将const int * 类型的值分配到int *类型的实体

ps = &a;//指向合法
*ps = 20;//error表达式必须是可修改的左值
return 0;
}


我们可以看到const定义的变量值不可修改,定义第一个指针指向它的地址也不可以,但第二个

指针却可以,这是因为第一个普通的int*类型指针与const int类型的值类型不同,但是我们

也可以看到,第二个指针岁指向成功但仍不可修改其值。进一步说明了const的修饰作用。

(定义指针指向一个const时编译器会认为你想修改地址内的值)

#include <stdio.h>
int main()
{
const int arr[3] = { 0 };
int *p;
const int *ps;
arr[0] = 1;//error表达式必须是可修改的左值
p = arr;//error不能将const int * 类型的值分配到int *类型的实体

ps = arr;//指向合法
ps[0] = 10;//error表达式必须是可修改的左值
return 0;
}


这次我们定义了一个数组并赋初值0,但我们可以看到在const定义了一个数组之后,

数组里面的元素便不可再修改,定义指针时与前面的变量的操作是一样的,只有const

指针指向数组名(数组首元素的首地址)时合法的,但仍不可修改数组元素。

E.g(typedef):

#include <stdio.h>
#define PINT int *
typedef int *pint;
int main()
{
pint a, b;
PINT c, d;
int p;
a = &p;
b = &p;
c = &p;
d = &p;//error 不能将int*类型的值分配到int类型的实体
return 0;
}


从上面的程序可以看出,使用typedef时与直接使用 int* 是一样的功能。

但使用define时却只能对第一个变量起作用,原因就是define只做字符的替换,

既没有计算表达式也没有赋予它所定义的符号真正的定义功能。

对于define 可以看下面这个例子:

E.g(define):

#define N 3
#define Y(n) ((N+1)*n)
执行语句z=2*(N+Y(5+1));
那么z = 2 * (3 + (3 + 1) * 5 + 1) = 48


在这题中 n = 5 +1 直接替换,并不计算。

再看下面这个例子:

#include <stdio.h>

int max(int x,int y)
{
return x > y ? x : y;
}

int main()
{
int x = 5;
int y = 8;
int z = max(x++,y++);
printf("x = %d, y = %d, z = %d\n", x, y, z);
}


这里很简单就打印 x=6,y=9,z=8.

因为这里是y++.所以z取了++之前的值。

那我们再看下面这个:

#include <stdio.h>
#define max( a,b) (((a)>(b))?(a):(b)) //从右至左
int main()
{
int x = 5;
int y = 8;
int z = max(x++,y++);
printf("x = %d, y = %d, z = %d\n", x, y, z);

int a = 5;
int b = 8;
int p = (((a++) > (b++)) ? (a++) : (b++));
printf("a = %d,b = %d,p = %d",a,b,p);
return 0;
}


打印的是 x=6,y=10,z=9. a=6,b=10,p=9.

为了更好解释,我们加入a,b,p这三个值。可以看到这两组值是相同的,所以就很很好理解了,在调用define定义的max宏时,只进行了字符替换,原式就变为了p的表达式。a++,b++,之后不满足 > 号,取分号后面的值,又进行了一次b++,所以b=10.

2.5 const 可以节省空间,避免不必要的内存分配。

const定义常量从汇编的角度来看,只是给出了对应的内存地址。所以,const定义的常量在程序运行过程中只有一份拷贝(因为是全局的只读变量,存在静态区),而 #define定义的常量在内存中有若干个拷贝(只要用到就会有拷贝)。

2.5 宏替换只作替换,不做计算,不做表达式求解;宏预编译时就替换了,程序运行时,并不分配内存。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐