C++指针详解

指针

一,基础知识
1,内存(memory)
电脑是在CPU里面执行任务的,CPU就相当于人的大脑。CPU计算速度很快,所以我们写程序时,哪怕例如执行100多次循环,看起来似乎都是一秒就能执行好的。然而,CPU的存储能力很差,里面自带的几个寄存器(以32位为例)才只能存储区区32个字节。就算把段寄存器全部用上,也才只有44字节。所以CPU就需要一个东西辅助存储数据。这个存储数据的地方就是内存。
内存就像一个超大规模的住宅区,里面存储着CPU需要执行的数据。相对CPU来说,其实内存是一个外部寄存器。一般来说,程序都是保存在内存中执行的,CPU必须从内存读入执行数据。
2,内存地址
内存很大,为了识别每个内存,我们就需要给每个内存编一个号,这个号码就是内存地址。

二,指针
1,先来看一个例子:
#include<stdio.h>
int main(void) {
  int x=0;
  printf("%i",&x);
}

 

这个时候程序会输出一个奇怪的数字,不是0。这个数据在不同机器执行的时候会有一些变化,所以这里就不写出执行后输出的值了。
这里“&”是取地址操作符,表示取变量x的地址。这时候输出的就是x的地址。
2,什么是指针
先来看一个例子。
#include<stdio.h>
int main(void) {
  int a=10;
  int *p;
  p=&a;
  printf("%i",*p);
}

 

执行后会输出10。这里我们重点看第4行和第5行。
int *p表示声明指针变量p。p存放的就是一个内存地址,用“*”指针操作符就可以得到p存放的内存地址所指向的内容了。也就是说,p=&a表示把a的地址赋值给p,然后此时*p的值就是&a地址对应的值,就是a。
3,用指针取得最大值
现在有2个数,请用指针变量取得这2个数的最大值。
#include<stdio.h>
int main(void){
  int a,b,*p;
  scanf("%i%i",&a,&b);
  if(a>b)p=&a;//p指向a
  else p=&b;//p指向b
  printf("%i",*p);
}

 

三,指针的加减运算
1,数组
定义数组,我们一般用如下的语句:
int a[10];
这个时候操作系统就会给数组a分配10个连续的地址。注意是连续的。

2,指针的加减运算
#include<stdio.h>
int main(void) {
  int a[3]={1,2,3};
  int *p;
  p=&a[0];
  printf("%i", *(++p) );
} 

 

此时会输出2.我们看一下我们的内存:
设程序是从地址main开始运行的,那么内存如下
main (a[0])
main+4 (a[1])
main+8 (a[2])
我们一开始把p指向a[0]所在的地址。++p,就是p的地址往前一个数,增加了sizeof(int),所以就是main+4,对应的数据就是2.
同理,p--就是往前一个地址,到达前面一个数据的空间。

四,指针和数组
1,
#include<stdio.h>
int main(void) {
  int a[5]={10,20,30,40,50};
  int *p;
  p=&a[0];
  printf("%i\n",*(p+2));
  printf("%i\n",a[2]);
}

 

输出结果应该是两个30.p的地址就是a[0]的地址,那么p+2就是a[2]的地址,所以*(p+2)就是a[2]。
同理我们知道,其实p就是a(数组名就是数组第一个元素的地址),所以,
*(p+i)=p[i]
其实根据加法交换率,*(p+i)=*(i+p)=i[p]。你可能不相信,把p[i]写成i[p]这样也没错。说白了数组其实就是指针。
为了写起来简便,我们一般把scanf中,&p[i]写作p+i。应该知道为什么吧。

2,(这个是新版本c++语言的写法)
#include<stdio.h>
int main(void){
  int n,*a,i;
  scanf("%i",&n);
  a=new int [n];
  for(i=0;i<n;i++){
    scanf("%i",a+i);
  }
  for(i=0;i<n;i++){
    printf("%i ",a[i]);
  }
}

 

首先输入一个数组元素个数,然后输入数组中的元素,最后输出数组。
这里a=new int [n]表示a向操作系统申请n个空间,这样a就可以当作数组使用了。否则a刚定义时,只有一个空间,不能往后继续存储(要不然就是数组越界),必须先向操作系统申请才能继续。
new是c++新引入的操作符:申请空间。这样做可以避免定义大数组时空间浪费。

顺便说一句,这是c++新引入的东西,老版本c语言是不支持的,所以编译的时候要注意。用c语言编译会出错。因为它不知道new是什么东西。
[Error] 'new' undeclared (first use in this function)

当然,老版本c语言也可以做类似的事情:动态分配内存。例如,
#include<stdio.h>
#include<stdlib.h>
int main(void){
  int n,*a,i;
  scanf("%i",&n);
  a=malloc(n*4);
  for(i=0;i<n;i++){
      scanf("%d",a+i);
  }
  for(i=0;i<n;i++){
      printf("%d ",a[i]);
  }
  return 0;
}
1,malloc函数包含在stdlib.h中,使用时要注意#include<stdlib.h>。
2,malloc函数用法:
(指针)=malloc(大小)。注意大小要乘以变量所占的字节数。(new不需要)
例如int占4字节,所以malloc参数就是n*sizeof(int)=n*4。
运行效果相同。
五,指针函数参数调用
现在要编写一个自定义函数swap(a,b),交换a,b变量的值。
错误示范1:
void swap(int a,int b){
  a=b;
  b=a;
  
}

 

错误:因为在a=b赋值完成后,a已经是b的值了,所以此时执行b=a,a已经不是原来的值了,错误。
错误示范2:
void swap(int a,int b){
  int tmp;
  tmp=a;//先把a保存起来
  a=b;
  b=tmp;
}

 

还是错误的。不信你运行一下试试,数值根本没变。
然而这么写却是对的。
int main(void){
  int a,b,tmp;
  scanf("%i%i",&a,&b);
  int tmp;
  tmp=a;//先把a保存起来
  a=b;
  b=tmp;
  printf("%i %i",a,b);
}

 

这是怎么会是呢?我们来了解一下函数的传值机制。
首先执行swap函数,参数把main里面的a和b传递给swap里面的a和b。方便描述,我们这里把main里面的用(main)表示,swap里面的用(swap)表示。
此时(swap)a=(main)a,(swap)b=(main)b。注意此时(swap)a和(main)a,(swap)b和(main)b地址不相等。
然后执行玩函数,(swap)a=(main)b,(swap)b=(main)a
然而(main)a,(main)b一点没变。
所以我们需要换一种传值方法。因为函数按值传递,所以我们传递函数的地址就可以了。
void swap(int *a,int *b){
  int tmp;
  tmp=*a;
  *a=*b;
  *b=tmp;
}

 

然后再写main。
错误示范3:
int main(void){
  int a,b,tmp;
  scanf("%i%i",&a,&b);
  swap(a,b);
  printf("%i %i",a,b);
}

 

首先下面出现了几行warning。(如果是C++,会显示Error)
[Warning] passing argument 1 of 'swap' makes pointer from integer without a cast
[Warning] passing argument 2 of 'swap' makes pointer from integer without a cast
然后运行时成功崩溃了。
(弹出“xxx已停止工作,windows正在查找该问题的解决方案”)
这又是为什么呢?因为函数中,函数参数传送的是地址,a和b都是地址变量,函数中调用*a和*b才是值,所以参数中应该是地址才对。
基本正确的程序示例:
int main(void){
  int a,b,tmp;
  scanf("%i%i",&a,&b);
  swap(&a,&b);
  printf("%i %i",a,b);
}

 

运行成功。我们来总结一下:
1,遇到修改变量值的函数,调用时要注意用指针,要不然传送时只修改了自定义函数的值,没有修改main函数中的值。
2,调用时注意加上地址符号“&”。是不是觉得这样写很像我们用scanf的时候,
scanf("%i",&a)
其实scanf也运用了指针,后面需要写传送参数的地址。

六,结构体指针
1,结构体指针就是指向结构体的指针,其内存地址就是该结构体的第一个元素的地址。
如:
struct student a;
struct student *stu;
*stu=&a;
此段代码中stu指针变量指向的就是a。
2,引用
(*stu).name
表示*stu的name成员。注意括号虽然不好看,但是不能省略。 *stu.name相当于:*(stu.name)。 当然,还有一种更加方便的写法,就是用箭头记号, stu->name。 也就是说, (*a).b=a->b
posted @ 2021-07-06 16:17  计算机知识杂谈  阅读(625)  评论(2)    收藏  举报