Repository navigation
Expand file tree
/
Copy pathconst关键字与指针.txt
More file actions
194 lines (166 loc) · 15.3 KB
/
Copy pathconst关键字与指针.txt
File metadata and controls
194 lines (166 loc) · 15.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
1.const 关键字,在c语言中用来修饰变量,表示这个变量是常量。
2.const修饰指针有4种形式
1. const int *p //p本身不是const,而p指向的变量是const的
2. int const *p //p本身不是const,而p指向的变量是const的
3. int * const p //p本身是const,而p指向的变量不是const
3. const int * const p; //p本身是const,p指向的变量也是const的。
3. 关于指针变量的理解,主要涉及到2个变量:一个是指针变量p本身,另一个是p指向的那个变量*p。
一个const关键字只能修饰一个。
4.const 修饰的变量真的不能改吗
const 修饰的变量其实也是可以改的,(gcc环境下)
在某些单片机环境下,const变量是不可以改的,取决于环境
在gcc中,const是通过编译器在编译的时候执行检查来确保实现的,
gcc把const类型的常量也放在了data段,其实和普通全局变量放在data段是一样的,只是编译器
认定变量是const的,运行时并没有标记const标志。
5. const是在编译器中实现的,编译时检查,并非不能骗过,所以在C语言中使用const,就好像是
一种道德约束而非法律约束。所以使用const时更多的是传递一种信息,就是告诉编译器,也告诉读
程序的人,这个变量是不应该被修改的。(不想被改变但是不能保证不被改变)
深入了解数组
1 数组变量也是变量,和普通变量和指针变量并没有本质的不同,变量的本质就是一个地址,这个地址在编译器
中决定具体数值,具体数值和变量名绑定,变量类型决定这个地址的长度。
2 变量、变量名、变量类型这三个概念的具体含义。
3 从内存角度 来讲,数组变量就是一次分配多个变量,而且这多个变量在内存中的存储单元是依次相连的。
我们分开定义多个变量(int a, b, c, d;)和一次定义一个数组(int a[4]).这两种方法的相同点是
都定义了4个int型变量,而且4个变量都单独使用。不同点是,单独定义时,a,b,b,d在内存中的
地址不一定相连。数组中的变量的地址是相连的,所以委适合用指针来操作。
地址的默认单位是一个字节,int a; 4字节,double a; 8个字节
注意数组和指针在初始化时和平时使用时的区别
区别a a[0] &a &a[0](前提int a[10];)
1 理解这些符号分别做左值和右值时的不同含义。
2 a是数组名,a做左值时表示整个数组的所有空间(10 * 4 BYTE),以因为c语言规定数组操作时要
独立操作,不能整体操作数组,所以a不能做左值,a做右值时表示数组首元素(数组中的第一个元素,
也就量a[0])的地址(首地址就是起始地址,就是4个字节中最开始的第一个字节的地址),也就是a做
右值等同于&a[0];
3 a[0] 表示数组的首元素,也就是数组的第0个元素。做左值时表示数组第0个元素对应的内存空间(连续4个字节)
做右值时表示数组第0个元素的值 (也就是数组第0个元素对应的内存空间中存储的那个数)
4 &a 就是数组名a取地址,就是数组的地址,&a不能做左值(&a实质是一个常量,不是变量因此不能赋值,所以自然不能做左值)
5 &a[0] 字面意思就是数组第0个元素的首地址([]和优先级高于&,即&(a[0]),做左值时表示数组首元素对应的内存空间,
做右值时表示数组首元素的地址,做右值时&a[0] 等同于a。
数组的地址是常量:因为数组 是编译器在内存中自动分配的,当我们每次执行程序时,运行时都会帮我们分配一块内存给这个数组,
只要完成了分配,这个数组的地址就定好了,本次程序运行直到终止都无法再改了,那么我们在程序中只能&a来获取这个分配的地址,
却不能用赋值增强版符修改它。
&a和a做右值时的区别:&a是整个数组的首地址,而a是是数组首元素的首地址即a[0]的地址。值相同,意义不同,意义不
相同会导致它们参与运算时会表现不同。
a和&a[0]做右值时的意义完全相同
&a是个常量,不能做左值
a做左值代表整个数组所有的空间,所有a不能做左值。
指针与数组配合使用
以指针的方式访问数组
数组元素使用时不能全体访问,只能单个访问,1 数组形式 2 指针形式
1 数组格式访问数组元素 数组名[下标] (注意下标从0开始)
2 指针访问数组元素 *(指针+偏移量) 如果指针是数组的首元素的地址(aa或者&a[0]),那么偏移量
就是下标。如果指针是其它哪个元素的地址,那么偏移量就要考虑叠加了。
3 数组下标方式和指针方式均可以访问数组元素,两者的实质其实是一样的,在编译器内部都是用
指针方式来访问数组元素的,数组下标方式只是编译器提供给编程者的一种壳(语法糖),
所以用指针方式来访问数组都是本质的做法。
数组中各个元素的地址是依次相连接的,而且数组还有一个很大的特点,就是数组中各个元素的类型必须相同,
类型相同就决定了每个数组元素占几个字节是相同的
数组中的元素其实就是地址相连、占地大小相同的一串内存空间,这两个特点决定了只要知道数组中
的一个元素,就可以很容易擅自出其它元素的地址。
int a[5];
int *p;
p = &a; //类型不匹配 a是数组类型的指针(数组指针:int (*)[]),而不是int * int (*)[]
int *p;
p = a;
//数组指针辨析
int arry[] = {1,2,3,4,5};
int (*p)[5] = NULL;
p = &arry;
printf("*p[i] = %d.\n", *p[i]);
printf("p[i] = %d.\n", p[i]);
printf("(*)p[i] = %d.\n", (*)p[i]);
p是int *,而&a是整个数组指针,也就是一个数组指针类型,不是int指针类型,所以不匹配
a &a &a[0]从数值上来看是完全相等的,但是从意义来看就不同了。从意义上来看,a和&a[0]
是数组首元素首地址,而&a是整个数组的首地址。从类型来看,a和&a[0]是元素指针,也就是int *类型
面&a是数组指针,是int (*)[5]类型
指针参与运算时,因为指针变量本身存储的数值是表示地址的,所以运算也是地址的运算。
指针参与运算的特点是,指针变量+1,并不是真的+1,而是+(1 * sizeof(指针类型))。如果
是int *指针,则+1就实际表示地址+4,如果是char *指针,则+1就表示地址+1,如果是double *指针,
则+1就表示地址+8。
指针变量+1实际地址不是+1而是+(1 * sizeof(指针类型)),主要是为了+1时正好指向下一个元素。
指针和强制类型转换
变量的数据类型的含义
所有的数据在内存中都以二进制的等式存储的,所以内存中只有0和1,并不是什么int char ...
int char short属于整型,他们的存储方式(数转换成二进制往内存中放的方式)是相同的,只是
内存格子的大小不同(所以这几种整型就彼此叫二进制兼容格式)而float和double的存储方式彼此不同,
和整形更不相同。
int a = 5;时,编译器给a分配4字节空间,并且将5按照int类型的存储方式转成二进制存到a所对应
的内存空间中去(a做左值)。我们printf去打印的时候(a做右值),printf会按照vsprintk()的格式化
字符串(就是printf传参的第一个字符串参数中的%d之类的东西。)所代表的类型去解析解析a所对应的
内存空间,解析出的值用来输出。也就是说,存进去晨是按照这个变量本身的数据类型来存储的(比如本例中的a是按照int型格式来存储的).但是取出来时是
按照printf中的%d之类的格式化字符串来提取的。此时虽然a所代表的内存空间中的10101序列并没有变(内存是没有被修改的)。
c语言中的数据类型的本质就是决定了这个数在内存中怎么存储的问题,也就是决定了这个数如何转成二进制的问题。一定要记住内存只是存储二进制序列,
而不管怎么解析。所以要求我们平时数据类型不能乱搞。要想到对应。
指针的数据类型的含义
指针的本质是:变量,指针就是指针变量。
一个指针涉及两个变量:一个是指针变量自己本身,一个是指针变量指向的那个变量
int *p;定义指针变量时,p(指针变量的本身)是int *类型的,*p(指向的变量的本身)是int类型的
int *就是指针类型,只要是指针类型就都是占4个字节,解析方式都是按照地址的方式来解析(意思是里面存的32个二进制加起来表示一个内存地址).结论就是:所有的指针类型(int *, char *, double *)的解析方式是相同的,都是地址。
对于指针所指向的那个变量来说,指针的类型就很重要了,指针所指向的那个变量的类型(它所对应的内存空间的解析方法)要取决于指针类型,比如指针是int *的,那么指针所指向的变量就是上int类型的。
指针、数组和sizeof运算符
1 sizeof是c语言的一个运算符(sizeof不是函数),用来返回()里面的变量或者数据类型占用
的内存字节数。
2 sizeof 主要是因为在不同的平台下,各种数据类型所占的内存字节数不尽相同。所以程序中
需要使用sizeof来判断当前变量/数据类型在当前环境下占几个字节。
3 32位系统中,所有指针的长度都4 Byte
4 strlen是一个C库函数,用来返回一个字符串的长度(不包括字符串末尾的"\0")
5 sizeof(数组名)的时候,数组名不做左值也不做右值,就是数组名含义。那么sizeof(数组名)
返回的是整个数组所占用内存空间(以字节为单位的)。
6 函数传参,形参是可以传数组的。函数形参是数组时,实际传递的不是整个数组,而是数组的
首元素首地址。
指针与函数传参
1 普通变量传参
1) 函数传参时,普通变量作为参数时,形参和实参的名字可以相同也可以不同。
2) 函数在子函数内部,形参的值等于实参,原因是函数调用时把实参的值赋值给了形参。
这就是“传值调用”,(实参做右值,形参做左值)
3) 函数名作为形参时,实际传递的不是整个数组,而是数组 的首元素的首地址(也就是整个
数组的首地址,因为传参时是传值)。所以在子函数内部,传进来的数组各就等于是一个指向
数组首元素首地址的指针,所以sizeof(a)为4.
4) 在子函数骨传参得到的数组首元素首地址,和外面得到的数组首元素首地址的值是相同的。
把这种特性叫做"传址调用"(所谓的传址调用就是调用时传进去了地址,也就是指针),
此时可以通过传进去的地址来访问实参。
5 数组作为形参的时候,[]里的数字是可有可无的,因为数组名作为形参传递的是个指针,
根本没有数组的长度信息。
6 指针作为函数形参和数组作为函数形参是一样的,指针方式访问数组元素和数组方式访问数组
元素的结果是一样的。
7 结构体变量作为函数形参的时候,实际上和普通变量(类似于int之类的)传参时表现是一模一样的,所以
说结构体变量其实也是普通变量而已。
8 因为结构体一般都很大,所以如果直接用结构体变量进行传参,那么函数调用效率就会很低。
(因为在函数传参的时候 需要将实参赋值给形参,所以当传参的变量越大效率越低)
9 结构体因为太大,所以传参应该用指针来传
传值调用的传址调用(两个数交互的例子)
传值调用描述的是这样一种现象:x和y作为实参,在swap中交互的是拷贝的副本。和原来的
值没有关系
10
输入型参数和输出型参数
1 为什么函数需要形参与返回值
1 函数名是一个符号,表示整个函数代码段的首地址,实质是一个指针常量,所以在程序吓使用
到函数名时都是当地址用的,用来 调用这个函数的。
2 函数体是函数的关键,由一对{}括起来,包含很多句代码,函数体就是函数实际做的工作
3 形参列表和返回值。形参是函数的输入部分,返回值是函数的输出部分。对函数最好的理解就是
把函数看成是一个加工机器(程序其实就是数据处理、加工),形参列表就是机器的原材料输入端。
而返回值就是机器的成品输出端。
4 其实没有形参列表和返回值,函数也能对数据进行加工,用全局变量即可。
5 全局变量传参最大的好处就是省略了函数传参的开销,所以效率要高一些,但是实战中用的最多
的还是传参,如果参数很多传参开销非常大,通常的做法是把很多参数打包成一个结构体,然后传
结构体变量指针进去。
6 函数传参中使用const指针。
1 const一般用在函数列表中,用法是const int *p;(意义是指针变量p本身是可变的,而p所指向的
变量是不可变的)。这种用法为了告诉程序,在函数中这个变量是不可改变的。
2 const用来修饰指针做函数传参,作用就在于声明在函数内部不会改变这个指针所指向的内容,
所以给该函数抟一个不可改变的指针(char *p = "linux";这种)不会触发错误,而一个未
声明为const的指针的函数,你给他传一个不可更改的指针的时候就要小心了。
3 一般来说,函数输入部分就是函数参数,输出部分就是返回值。问题在于函数的参数可以有很多个,
而返回值只能有1个。这就造成我们无法让一个函数返回多个值。
4 现实编程中,一个函数需要返回多个值是非常普遍的,因此完全依赖于返回值是不靠谱的。
通常的做法是用参数来做(在典型的linux风格函数中,返回值是不用来返回结果的,而是用来返回0
或者负数用来表示程序执行结果是对还是错,是成功还是失败)
5 普遍做法,编程中函数的输入和输出都是靠参数的,返回值只是用来用来返回0或者负数
用来表示程序执行结果是对还是错,是成功还是失败。如果这个参数是用来做输入的,就叫输入型参数,
如果这个参数是用来做输出的,就叫输出型参数。
6 输出型参数就是用来让函数内部把数据输出到函数外部的
——看到一个函数的原型后,怎么一眼看出哪个参数是输入参数,哪个参数是输出参数。其实函数
传参如果传的是普通变量,那肯定是输入型参数,如果传指针就可能是输入参数,也可能是输出参数。
——为了区别,经常的做法是:如果这个参数是做输入的(通常做输入的在函数内部只需要读取这个参数而
不会需要更改它)就在指针前面加const来修饰; 如果函数形参是指针变量并且还没加const,
那么就表示这个参数是用来做输出型参数的。