1:指针

查看变量的地址的方式

int a = 10;

printf("%08X%d\n",&a);

或者 printf("%p\n",&a);

表示地址的方式(指针)

int a = 5;

int* pa = &a;

表示 pa指向了一个int型变量  也可以称其为pa所在的内存为一个int型的整数

我们成int*为指针类型,pa为指针类型的变量(简称指针)

关于指针

1)指针是变量,是可以变的。

int a = 10;

int b = 11;

int* p = &a; //p指向a

p = &b; //p指向b

2)不同类型的指针,是不能互相赋值的

int a = 5;

int* pa = &a;   //pa 指向a

double* pa = &a;  //错误提示  左边为double  右边为int

3)指针即地址,地址是整数,所以指针是一个整数类型

4)*操作

①已知一个int变量,可以用两种方式来改变他的值

int a  = 6;

a = 7;  //直接使用变量名赋值

int* p = &a;

*p = 12; //按地址访问,直接操作内存

②有一个指针变量p指向a,可以直接用*p来访问a

另外要注意 *只支持指针类型,别的类型用*会报错

2:指针与数组

在C++中,数组名就是地址,就是数组在内存中的位置,它表示第一个元素的地址。

int arr[4] = {5,7,3,8};

int* p = arr;  //p表示的是arr[0]的地址

相当于 int* p1 = &arr[0];

①指针的加减法

指针加法:后移N个元素

指针减法:前移N个元素

int arr[4] = {5,7,3,8};

int* p = arr;

p += 2;  //向后移动两位  取  a[2]  

printf("%d\n",*p);   //结果为 3

p -= 1;  //再向前移动一位  取 a[1]

printf("%d\n",*p);  //结果为 7

② 指针加减法和int加减法的区别

int arr[4] = {5,7,3,8};

int* p1 = arr;

int* p2 = p1 + 1;

printf("%d\n",*p2);

注意*p和p的区别

③用指针指向数组中任一个元素

例如指向  arr[3]

可以用  int* p = arr+3  或者  int* p = &arr[3]

④用指针给元素数组赋值

arr[3] = 10; //传统方法

int* p = arr+3;

*p = 10;   //指针方法1

*(arr + 3) = 10; //指针方法2

⑤指针也可以当成数组用

int arr[4] = {5,7,3,8};

int* p = &arr[1];

p[0] = 11111;  //p[0] 即从p开始的第0号元素,也就是arr[1]

p[1] = 22222;  //p[1] 也就是arr[2]

⑥数组的遍历

int arr[4] = {5,7,3,8};

传统方法

for(int i = 0; i<4;i++ ){

printf("%d\n",arr[i]);

}

指针的方法

for(int* p = arr;p<arr+4;p++){

printf("%d\n",*p);

}

p++表示指针向后移一位

3:指针作为函数的参数

大家先思考一个问题,把指针作为一个函数的参数,到底有什么用?我们先看下面的例子

1)传递普通的变量作为参数

void test(int* p){

printf("in the test: %d\n",*p);

*p += 1;

}

int main(){

int a = 0;

test(&a);

printf("in the main:%d\n",a);

return 0;

}

打印结果为:

in the test: 0

in the main:1

由此可见:使用指针作为参数,可以实现两种功能

①可以读取上一层函数中变量的值

②可以修改上一层函数中变量的值(普通的参数无法实现)

我们再来看几个例子,加深理解

例①求两个数的和

void sum(int* a,int* b,int* result){

*result = *a+*b;  //在函数sum中读取到*a 和*b 并且给*result赋值(当然在这里函数的参数的名字是可以任意取的)

}

int main(){

int a = 3;

int b = 10;

int result = 0;

sum(&a,&b,&result);  //将 a,b,result的内存地址(指针)传给函数sum

printf("the result is :%d\n",result);

return 0;

}

例② 调换两个变量的值

void swap(int* p1,int* p2){

int t = *p1;

*p1 = *p2;

*p2 = t;   //接收到a和b的指针之后进行调换

}

int main(){

int a = 3;

int b = 10;

swap(&a,&b);  //将a和b的内存地址(指针)传给函数swap

printf("a = %d,b = %d",a,b);

return 0;

}

2)传递数组作为参数

首先我们要明白在C++中数组是没有长度的概念的,数组只是表示一段连续的内存

所以在把数组信息传递给函数的时候需要传递

①首地址:一片连续内存地址

②长度:这块内存上存储的内存的个数

数组名实质上是一个指针类型,传递数组,也就是传递指针类型

void avg(int* arr,int len){

int i = 0;

float sum = 0;

for(i;i<len;i++){

sum+= arr[i];

}

sum = sum/len;

printf("%.2f\n",sum);

}

int main(){

int arr[] = {1,2,3,4};

avg(arr,4);  //2.5  从arr[0]到arr[3]

avg(arr,3);  //2   从arr[0]到arr[2]

avg(arr+2,2);//3.5  从arr[2]到arr[3]

return 0;

}

显然,对avg来说,不是关心你是不是叫数组,在他眼里,他只是接收了一个内存地址而已。

注意事项:

①以下两种方法完全等价,没有任何区别

int avg(int* p,int length)

int avg(int p[],int length)

②传递数组时,同时一定也要把数组的长度给传进去

我们看到了这里,总是会想传递指针到底优势体现在哪里?

优势①一般的return的形式只能返回一个值,而传递指针能够返回多个值

优势②传指针的效率更高(普通的传参数的形式相当于给参数创建了一个副本保存参数,占据了内存)

我们来看看优势①

求一个数组中的最大值和最小值(用直接传参数的方法很难一次性实现)

void max_and_min(int* arr,int length,int* max,int* min){

*max = arr[0];

*min = arr[0];

for(int i = 0; i < length;i++){

if(arr[i] > *max){

*max = arr[i];

}

if(arr[i] < *min){

*min = arr[i];

}


}

}

int main(){

int arr[] = {1,2,3,9,5,6,7,4};

int max = 0;

int min = 0;

max_and_min(arr,7,&max,&min);

printf("max:%d,min:%d",max,min);

return 0;

}

4:const指针

作用:const指针的作用是限制星号操作内存里的写入功能,称为:只读的readonly,这块内存只能读不能写

常用于修饰函数的参数,当参数被const修饰,表示该参数只用于读取,不能写入

const仅仅限制星号的参数的写内存,对于参数的加减(+-内存移位)是没有关系的

5:如何安全的使用指针

在使用指针之前,一定要弄清楚两个问题:

①这个指针指向了哪里?

②该指针指向的地址是否有效?

就目前来说,指针只能指向两个地方

①指向了变量,数组

②指向0(空指针)  注意  int a = 0; int* p = &a; 这种情况不属于空指针  int* p = 0;才属于空指针的情况,存在空指针的情况下,程序会崩溃

int* p;这种情况属于野指针情况,其值为随机值,此时程序指向了一个随机的内存地址,程序会直接崩溃掉,一定不能够出现

当指针为空指针时,不能使用*操作,但是空指针是可以接受的,可以用if来判断

if(p){

printf("%d\n",*p);

}

1)综上所述,在程序中杜绝使用野指针,空指针的话一定要先加if判断

2)在使用指针的时候,数组一定不能越界,这个问题不容易察觉

int arr[] = {1,2,3,4};

int* p = arr;  //指向首地址arr[0]

p+=4;//指向arr[4]  已经越界

*p = 12;

3)确定指针指向的变量是否已经失效

int main(){

int* p = 0;

if(1){

int a = 10;  //a生效

p = &a;    //p指向a

}  //a失效

*p = 11;   //p指向了一个无效的地址

printf("%d\n",*p);

return 0;

}

虽然上述程序可以成功运行,但是不代表能这么做,在后面我们学习了类的概念之后,才会意识到这个问题的严重性