上节介绍了二维数组。C/C++之二维数组
本节介绍劝退大部分同学的指针!(重点)。
由于比内容比较多,篇幅比较长,所以分为三个部分。本篇为上。
引入几个问题:
之前在讲函数的时候,提到过普通变量是无法通过调用函数来改变的。
例如:
#include
using namespace std;
void add(int a, int b){
a = a + b;
}
int main(){
int x = 0;
add(x, 5);
cout << x << endl;
return 0;
}
显然,这里的结果不会改变。x依旧是0,看输出:
0
如果一个函数需要返回多个值又该怎么做?
例如:一个函数要返回一个int值,同时要判断是否成功执行,还要返回bool类型值。这时候肯定不行的,因为一个函数只有一个返回值。
如果一次性传入太多的参数,无疑会大幅度拖慢速度,执行效率会被降低,这时候怎么做呢?还是使用指针。
上面引入的问题都对应了一个新的变量,指针变量。
本节来介绍听起来就比较难的指针(其实很简单。)
引入:scanf()
函数,大家在用scanf()
输入值的时候,后面的变量列表都加了取地址符&。这是为什么呢?
很明显,scanf()
函数传递变量只是普通的值传递,并不会改变变量的值,所以此处传递的是一个指针,一个地址。
scanf("%d", &a);
类似的情况。
实质:scanf()
把输入的常量,保存到地址对应的值中。
指针的定义: 使用变量类型* 变量名;
来定义一个指针。实例:
#include
using namespace std;
int main(){
int age = 18;
//整数类型的指针,指向一个整数的地址。此处p的值就是age的地址。
int* p = &age;
//调用scanf(),此处可以直接调用指针变量而不需要&(取地址),因为p的值本身就是一个地址
scanf("%d", p);
return 0;
}
定义:变量类型*变量名;
来定义,具体方式有如下几种。差别不大,建议用第一种和第二种。
int *p;
int* p;
int * p;
int*p;
(不建议)误区:int *p1, *p2;
这是两个指针变量,int* p1, p2;
这是一个指针变量p1和一个普通变量p2;(*连着int没啥特别的地方)。
强调:指针也有自己的地址。它的值为指向的变量的地址(不是指针的地址!)。
详解:
#include
using namespace std;
int main() {
int age = 18;
//整数类型的指针,指向一个整数的地址。此处p的值就是age的地址。
int* p = &age;//等价为int *p; p = &age;
//获取指针变量p的地址。
printf("指针p的地址为:%p\n", &p);
//获取指针p指向的地址。
printf("指针p所指向地址为:%p\n", p);
//获取指针p指向的地址的值。
printf("指针p所指向地址的值为:%d\n", *p);
//获取普通变量age的地址。
printf("普通变量p的地址为:%p\n", &age);
//获取普通变量age地址中的值。
printf("普通变量p的地址对应的值为:%d\n", *(&age)); //等同于先取地址,再取地址中的值
return 0;
}
输出结果:(每次调试都不同, 原因:程序结束自动释放内存)
指针p的地址为:010FFB10
指针p所指向地址为:010FFB1C
指针p所指向地址的值为:18
普通变量p的地址为:010FFB1C
普通变量p的地址对应的值为:18
分析: 此处获得了两个不同的地址值,指针的地址和指针指向的地址(分别为010FFB10与010FFB1C)。指针指向的地址和指向的变量的地址相同(此处为010FFB1C),同时指向的地址的值与指向的变量的值也相同。
用图表示为:
指针和其他的普通变量一样,都可以访问和修改。
例如:(&取地址符)
#include
using namespace std;
int main() {
int age1 = 18;
int age2 = 21;
int age3 = 24;
int* p = &age1;
//使用%d打印地址可能会出现负数。
printf("p的地址为: %p\t age1的地址为: %p\n", p, &age1);
//改变p指向的变量。
p = &age2;
printf("p的地址为: %p\t age2的地址为: %p\n", p, &age2);
//改变p指向的变量。
p = &age3;
printf("p的地址为: %p\t age3的地址为: %p\n", p, &age3);
return 0;
}
输出内容:
p的地址为: 00F1F9A4 age1的地址为: 00F1F9A4
p的地址为: 00F1F998 age2的地址为: 00F1F998
p的地址为: 00F1F98C age3的地址为: 00F1F98C
发现:改变指针所指向的对象后,它的值变成了指向的对象的地址。(使用&取出变量的地址)
访问指针所指向的地址的值。(*
为间接访问符,指针运算符)
#include
using namespace std;
int main() {
int age = 18;
int* p = &age;
int x = 0;
//间接访问指针p指向地址所对应的值
x = *p;
//使用%d打印地址可能会出现负数。
*p = 13;
printf("x的值为:%d\n", x);
printf("*p的值为:%d\n", *p);
return 0;
}
输出结果:
x的值为:18
*p的值为:13
可以通过 *
(指针运算符)来取出指针指向的变量的地址的值,并且和别的非指针变量一样能够进行相同的运算。
看下面的例子:
#include
using namespace std;
int main() {
int age;
int* p;
cin >> age;
if(age == 18){
p = &age;
}else if(age == 40){
p = &age;
}
cout << "p指向的地址为:" << p << endl;
return 0;
}
上面的代码存在很大的问题:可能会没有初始化指针p的值。而且会报错。
在定义指针的时候就初始化来解除警报。int* p = 0;
或者int* p = NULL;
初始化为空指针。
但0地址是不可访问的,所以需要将代码变得更加健全!
#include
using namespace std;
int main() {
int age;
int* p;
cin >> age;
if(age == 18){
p = &age;
}else if(age == 40){
p = &age;
}
if(p){
cout << "p指向的地址为:" << p << endl;
}else{
cout << "p没有指向任何地址" << endl;
}
return 0;
}
注意:一般使用NULL而不使用0来初始化指针
坏指针:没有初始化就使用的指针!
数组和普通变量不同,前面通过普通函数发现能够改变数组中成员的值。C/C++之自定义函数
原理:传递整个数组到函数中,不是单纯的传递值,而是传递数组的第零个成员的地址!!
对数组的地址进行分析:
#include
using namespace std;
int main() {
//定义两个数组,一个为int型,一个为char型
int a[3] = {
1, 2, 3};
char b[3] = {
'a', 'b', 'c'};
//获取a数组的所有成员的地址。
printf("a[0]的地址为:%p\n", &a[0]); //等效于printf("a[0]的地址为:%p\n", a);
printf("a[1]的地址为:%p\n", &a[1]); //等效于printf("a[0]的地址为:%p\n", a + 1);
printf("a[2]的地址为:%p\n", &a[2]); //等效于printf("a[0]的地址为:%p\n", a + 2);
//使用第二种取地址的方法取值
printf("a[0]的值为:%d\n", *a);
printf("a[1]的值为:%d\n", *(a + 1));
printf("a[2]的值为:%d\n", *(a + 2));
//获取b数组的所有成员的地址。
printf("b[0]的地址为:%p\n", &b[0]); //等效于printf("b[0]的地址为:%p\n", b);
printf("b[1]的地址为:%p\n", &b[1]); //等效于printf("b[0]的地址为:%p\n", b + 1);
printf("b[2]的地址为:%p\n", &b[2]); //等效于printf("b[0]的地址为:%p\n", b + 2);
//使用第二种取地址的方法取值
printf("b[0]的值为:%c\n", *b);
printf("b[1]的值为:%c\n", *(b + 1));
printf("b[2]的值为:%c\n", *(b + 2));
return 0;
}
输出:
a[0]的地址为:012FF904
a[1]的地址为:012FF908
a[2]的地址为:012FF90C
a[0]的值为:1
a[1]的值为:2
a[2]的值为:3
b[0]的地址为:012FF8F8
b[1]的地址为:012FF8F9
b[2]的地址为:012FF8FA
b[0]的值为:a
b[1]的值为:b
b[2]的值为:c
大家发现没有:之前讲指针的定义的时候int *p的地址占了四个字节(010FFB10)。
这里也相同,int a[3]数组,每个成员占四个字节,而char b[3]数组,每个成员占1个字节。
总结:int* p = a;
表示指向数组的指针,访问数组的第i个元素可以用*(p + i)
来访问!
指针的自增:(自增与自减++,–的优先级高于*)
#include
using namespace std;
int main() {
int a[5] = {
1, 2, 3, 4, 5};
int* p = a;
//用指针输出值
for(int i = 0; i < 5; i++){
printf("%d ", *p++);
}
return 0;
}
输出结果:
1 2 3 4 5
指针的自减:
#include
using namespace std;
int main() {
int a[5] = {
1, 2, 3, 4, 5};
//让p指向最后一个元素的地址
int* p = &a[sizeof(a) / sizeof(a[0]) - 1];
//用指针逆序输出
for(int i = 0; i < 5; i++){
printf("%d ", *p--);
}
return 0;
}
输出结果:
5 4 3 2 1
注意:+,-运算符的优先级低于*(乘)(取值运算符)所以在具体访问数组中某个值时需要*(p + i);
例:
#include
using namespace std;
int main() {
int a[5] = {
21, 12, 66, 44, 25 };
//让p指向最后一个元素的地址
int* p = &a[0];
//用指针输出第四个*(p + 3)
printf("第四个元素的值为:%d\n", *(p + 3));
//注意区别
printf("*p + 3的值为:%d", *p + 3);
return 0;
}
输出结果:
第四个元素的值为:44
*p + 3的值为:24
分析: *(p + 3)
等同于 a[4]
, *p + 3
等同于 a[0] + 3
;
总结: 进行指针对数组中的元素进行取值时:p + i
实际指向的地址为p + i * sizeof(数据类型))
先来讲指针的减法:
用处:计算数组元素的偏移值,计算字符串元素的偏移值
#include
using namespace std;
int main() {
int a[5] = {
21, 12, 66, 44, 25 };
int* p1 = &a[0];
int* p2 = &a[4];
printf("p2 - p1 = %d\n", p2 - p1);
printf("p1 - p2 = %d", p1 - p2);
return 0;
}
输出结果:
p2 - p1 = 4
p1 - p2 = -4
相减的指针如果不是指向同一个数组,那就没有意义。
指针的相加:(无任何意义,故在此不做研究。)
本节到这里,下一节继续。