一、细说指针
指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。
要搞清一个指针需要搞清指针的四方面的内容:
- 指针的类型
- 指针所指向的类型
- 指针的值或者叫指针所指向的内存区
- 指针本身所占据的内存区
让我们分别说明。先声明几个指针放着做例子:
int*ptr;
char*ptr;
int**ptr;
int(*ptr)[3];
int*(*ptr)[4];
1、指针的类型
从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的类型:
- int*ptr;//指针的类型是int*
- char*ptr;//指针的类型是char*
- int**ptr;//指针的类型是int**
- int(*ptr)[3];//指针的类型是int(*)[3]
- int*(*ptr)[4];//指针的类型是int*(*)[4]
2、指针所指向的类型
当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。例如:
- int*ptr; //指针所指向的类型是int
- char*ptr; //指针所指向的的类型是char
- int**ptr; //指针所指向的的类型是int*
- int(*ptr)[3]; //指针所指向的的类型是int()[3]
- int*(*ptr)[4]; //指针所指向的的类型是int*()[4]
在指针的算术运算中,指针所指向的类型有很大的作用。
指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当你对C 越来越熟悉时,你会发现,把与指针搅和在一起的"类型"这个概念分成"指针的类型"和"指针所指向的类型"两个概念,是精通指针的关键点之一。我看了不少书,发现有些写得差的书中,就把指针的这两个概念搅在一起了,所以看起书来前后矛盾,越看越糊涂。
3.指针的值----或者叫指针所指向的内存区或地址
指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32 位程序里,所有类型的指针的值都是一个32 位整数,因为32 位程序里内存地址全都是32 位长。指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为si zeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX 为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。
以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?(重点注意)
二、const int* pi 与 int* const pi 的区别
-
从const int i 说起
int i = 0;
i=20; //可以重新赋值
const int i2 = 20;
i2 = 40; //错误,无法对const修饰的i2赋值
有const修饰的i2称为符号常量,不能重新赋值
两种写法
const int i2 = 20;
int const i2 = 20;
-
const int* pi
int i1 = 30;
int i2 = 40;
const int *pi = &i1;
pi = &i2; /* 注意这里,pi可以在任意时候重新赋值一个新内存地 址*/
i2 = 80; /* 想想看:这里能用*pi = 80来代替吗?当然不能!*/
printf("%d\n", *pi); /* 输出是80 */
pi的值(pi是存储的是值为内存地址的指针变量)是可以修改的 ,即重新指向另一个地址
但是不能通过 *pi 去修改i2的值。因为const 修饰的是 *pi 所以 *pi 就称为了常量,是不能修改的
-
int* const pi
int i1 = 30;
int i2 = 40;
int *const pi = &i1;
/* pi = &i2; 注意这里,pi不能再这样重新赋值了,即不能再指向另一个新地址*/
i1 = 80; /* 想想看:这里能用 *pi = 80; 来代替吗?可以,这 里可以通过*pi修改i1的值。
printf("%d", *pi); /* 输出是80 */
pi的值是不能重新赋值修改了,只能永远指向初始化时的内存地址
相反,可以通过*pi 来修改i1 的值了
总结:
- 如果 const 修饰在*pi 前,则不能改的是*pi (即不能类似这样: *pi=50;赋值) 而不是指pi。
- 如果const 是直接写在pi前,则pi不能改(即不能类似这样:pi=&i; 赋值)。
三、函数参数传递
浅谈C++中指针和引用的区别
-
值传递
//交换
void Exchg1(int x, int y){
//int x = a;
//int y = b; /* ← 注意这里,头两行是调用函数时的隐含操作 */
int tmp;
tmp = x;
x = y;
y = tmp;
printf("x = %d, y = %d\n", x, y);
}
void main()
{
int a = 4,b = 6;
Exchg1(a, b);
printf("a = %d, b = %d\n", a, b);
}
-
地址传递
void Exchg2(int *px, int *py)
{
// px = &a; /* ← */
// py = &b; /* ← 请注意这两行,它是调用Exchg2的隐含动作。*/
int tmp = *px; //通过地址去修改值
*px = *py;
*py = tmp;
printf("*px = %d, *py = %d.\n", *px, *py);
}
void main() {
int a = 4;
int b = 6;
Exchg2(&a, &b);
printf("a = %d, b = %d.\n", a, b);
}
-
引用传递
void Exchg3(int &x, int &y){ /* 注意定义处的形式参数的格式与 值传递不同 */
int tmp = x;
x = y;
y = tmp;
printf("x = %d, y = %d.\n", x, y);
}
void main(){
int a = 4;
int b = 6;
Exchg3(a, b); /*注意:这里调用方式与值传递一样*/
printf("a = %d, b = %d.\n", a, b);
}
四、*p++,(*p)++,*++p有什么不同
- *p++: 等价于 *(p++),表示先取指针指向的值,再将指针p+1(当指针指向数组元素的时候,p+1 并不是简单地将地址加 1,而是指向数组的下一个元素)
- (*p)++:表示先取指针p指向的值,再将该值自增1
- *++p :等价于 *(++p),会先进行 ++p 运算,使得 p 的值增加,指向下一个元素,整体上相当于 *(p+1),所以会获得第 n+1 个数组元素的值
五、左值
C 语言的术语 lvalue 指用于识别或定位一个存储位置的标识符。(注意:左值同时还必须是可改变的)
六、指针和数组
1、指针和数组的区别
- 指针是左值
- 而数组名只是一个地址常量,它不可以被修改,所以数组名不是左值。
2、指针数组
它实际上是一个数组,数组的每个元素存放的是一个指针类型的元素。
int* arr[8];
//优先级问题:[]的优先级比*高
//说明arr是一个数组,而int*是数组里面的内容
//这句话的意思就是:arr是一个含有8和int*的数组
3、数组指针
它实际上是一个指针,该指针指向一个数组。
int (*arr)[8];
//由于[]的优先级比*高,因此在写数组指针的时候必须将*arr用括号括起来
//arr先和*结合,说明p是一个指针变量
//这句话的意思就是:指针arr指向一个大小为8个整型的数组。
void main(){
int nums[5] = {1,2,3,4,5};
int (*p)[5] = &nums; //指向数组的指针(数组指针) &不要忘
printf("nums: %p\n",nums);
printf("&nums: %p\n",&nums);
printf("&nums[0]: %p\n",&nums[0]);
printf("*p: %p\n",*p);
printf("%d\n",sizeof(*p));//*p是数组的首地址所以外面需要再来个*
for(int i = 0; i < 5; i++){
printf("%d\n",*(*p+i));
}
}
4、指针和二维数
来源: