一、 指针
1. 什么是指针
指针其实就是一个变量,不过它的值是一个内存地址 , 这个地址可以是变量或者一个函数的地址
当你声明明一个变量的时候,计算机会将指定的一块内存空间和变量名进行绑定;这个定义很简单,但其实很抽象,例如:int x = 5; 这是一句最简单的变量赋值语句了, 我们常说“x等于5”,其实这种说法是错误的,x仅仅是变量的一个名字而已,它本身不等于任何值的。这条statement的正确翻译应该是:“将5赋值于名字叫做x的内存空间”,其本质是将值5赋值到一块内存空间,而这个内存空间名叫做x。切记:x只是简单的一个别名而已,x不等于任何值。
不就是使用变量或者调用函数吗?难道不能直接调用吗?那么需要指针做什么呢?
| 实际上是可以的,但是并不是所有的情况都可以。比如:
1. 在内部函数中,可以使用指针访问外部函数中定义的某个变量x, 因为它并不是声明在自己的函数范围内。
2. 指针在处理函数传递数组的时候非常高效
3. 我们还可以在堆中申请一块动态内存,这块内存甚至没有一个变量名称,唯一的访问方式是通过指针。
4. 可以使用你指针访问指定的内存地址(游戏修改器)
|
2. 指针使用
1. 声明指针
1.声明指针的时候要记得初始化,如果没有初始化,指针存放的将会是垃圾数据(因为你根本不知道它指向何方)
- 可以使用nullptr(c++11)进行指针初始化,初始化存放的值是 0
| 变量类型 *指针名称;
int * int_ptr;
double * double_ptr;
char * char_ptr;
string * strng_ptr;
|
2. 初始化指针
指针的指向是一个块内存地址,如果仅仅是声明而未初始化,那么指针的指向无法得到保证,如果没有明确、指针的指向,那么最好把它初始化成一个空指针,也就是表示目前没有指向,空指针的值是0
| //初始化指针
int a = 3 ;
int *p0 = &a; //p0是一个指针,指向的是一个int类型的数据,这个数据是3.
//空指针
int *p1 = nullptr;
int *p2 = NULL;
int *p3 = 0 ;
|
3. 指针地址和大小
指针实际上也是一个变量,也会有自己的内存空间,也会有自己的长度大小。获取指针的内存地址,依然使用取地址符 &
, 长度大小依然使用sizeof
来获取
| int age = 88;
int *p = &age;
cout << "指针的地址是: " << &p <<endl;
cout << "指针存储的是: " << p <<endl;
cout << "指针大小是: " << sizeof p <<endl;
cout << "age的大小是: " << sizeof age <<endl;
|
3. 指针dereference( 解引用)
所谓的指针dereference就是,指针就是一个变量,存放的是一个地址。这个地址有可能是变量 a 或者是变量b的地址。有了这个地址,我们可以通过dereference操作符 *
去获取到a对应的值或者b对应的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | #include<iostream>
using namespace std;
int main(){
//定义一个变量score,赋值100
int score {100}
//定义一个指针score_ptr 指向score的地址。
int *score_ptr{&score};
//通过指针,获取到指向位置的数据 打印100
cout << *score_ptr << endl;
//使用指针修改原来的score
*score_ptr = 200 ;
//使用指针和变量的方式打印score,结果都输出200
cout << *score_ptr << endl;
cout << score << endl;
return 0 ;
}
|
4. 动态内存分配
在进行编码的时候,我们根本不知道需要多少内存空间。举个例子,比如我们需要存储学生的数据,这时候可以使用数组来存储,那么就必须知道学生的具体人数。如果不知道,就无法使用数组了。实际上之前学过的vector就是使用动态内存。但是有时候,我们如果需要存放的是一单个对象数据,并不是一堆数据。用vector就有点浪费了。
为了解决上述问题,C++ 提供了一种“动态内存分配”机制,使得程序可以在运行期间,根据实际需要,要求操作系统临时分配一片内存空间用于存放数据。此种内存分配是在程序运行中进行的,而不是在编译时就确定的,因此称为“动态内存分配”。申请动态内存
1. 申请内存
可以使用new
关键字来申请动态内存 , new
开辟出来的空间都位于堆内存中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | #include<iostream>
using namespace std;
int main(){
//定义一个int类型的指针,并没有指向任何地方。
int * int_ptr{nullputr};
//在堆中申请内存,使用指针指向
int_ptr = new int ;
//由于未赋值,所以输出的可能是未知的值
cout << *int_ptr << endl;
//修改开辟空间的数据值为100
*int_ptr = 100;
//解引用,输出100
cout << *int_ptr << endl;
return 0 ;
}
|
2. 释放内存
new
常和 delete
成对出现,使用 new
开辟空间, 使用 delete
释放申请的内存,避免造成内存泄漏
1
2
3
4
5
6
7
8
9
10
11
12
13 | #include<iostream>
using namespace std;
int main(){
int *int_ptr{nullptr};
int_ptr = new int ; //申请内存
...
delete int_ptr ; //释放内存
return 0 ;
}
|
3. 数组操作
使用new int[]
来给数组申请动态内存 , 然后使用 delete[]
释放申请的内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | #include<iostream>
using namespace std;
int main(){
int *array_ptr{nullptr};
int size{};
cout << “你期望的数组大小是:” << endl;
cin >> size ;
array_ptr = new int[size];
//释放申请的空间
delete [] arrray_ptr;
return 0 ;
}
|
4. 关于动态内存的思考
通常情况下,定义的变量存储的位置位于栈内存中,栈内存的数据,当函数执行结束后即会被释放,这是栈内存的机制自己决定的,并且栈内存中由于内存并不是太大,所以不建议大量的数据存放在栈内存。
而堆内存中的容量相比栈内存要大多了,但是堆内存并不提供回收释放的工作,允许程序申请内存空间,但是同时也要自己负责内存空间的释放工作。
不能一概而论哪一种是最优的决定,要根据开发场景来决定。
5. 数组和指针的关系
数组其实和指针是存在一些内在联系的,如下:
- 根据数组名字取到的内存地址,是数组的第一个元素地址
- 指针其实是一个变量,这个变量存放的值是内存地址
- 如果一个指针和数组是同样的类型,并且指针存放的地址正好是数组的某个元素地址,那么可以通过该指针操作数组
1. 数组与指针
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 | #include<iostream>
using namespace std;
int main(){
//定义3个长度的int类型数组
int scores []{100, 95 , 98};
//直接打印数组,实际上是打印数组第一个元素的地址 0x61fec8
cout << scores << endl;
//使用*操作符是根据地址获取数据,所以取到的是第一个元素 : 100
cout << *scores << endl;
//声明指针,存放的是数组第一个元素的地址
int *score_ptr{scores};
//打印指针,其实输出它保存的地址,即数组首元素地址 0x61fec8
cout << score_ptr << endl;
//解引用,输出的是数组的首元素 100
cout << *score_ptr << endl;
return 0 ;
}
|
2. 指针运算
如果指针指向的是数组的第一个元素地址,那么同样可以通过对指针进行加减运算,来获取其他的元素 。值得注意的是,指针的相加并不是单纯数字上的相加,而是指针对应类型占用字节的相加
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 | #include<iostream>
using namespace std;
int main(){
//定义3个长度的int类型数组
int scores []{100, 95 , 98};
//定义一个int类型指针,指向的是数组的首元素
int *score_ptr{scores};
//使用数组的手法打印数组
cout << score_ptr[0] << endl; //100
cout << score_ptr[1] << endl; //95
cout << score_ptr[2] << endl; //98
//对指针进行加法运算。由于score_ptr 是int类型,
//而int类型占用4个字节,所以每次相加打印出来的地址都会变长4个字节
cout <<score_ptr << endl; // 0x7fffacde9420
cout <<(score_ptr+1) << endl; // 0x7fffacde9424
cout <<(score_ptr +2) << endl; // 0x7fffacde9428
//指针解引用取值
cout <<*score_ptr << endl; // 100
cout <<*(score_ptr+1) << endl; // 95
cout <<*(score_ptr +2) << endl; // 98
return 0 ;
}
|
6. 指针算数
指针除了表示存储的是内存地址之外,它也可以做算术运算 和 比较大小。
指针可以用关系运算符进行比较,如 ==、< 和 >。如果 p1 和 p2 指向两个相关的变量,比如同一个数组中的不同元素,则可对 p1 和 p2 进行大小比较。
1. 指针递增
指针递增 , 如果是数组操作,可以指向后一个元素
| int score[]{90,85,100};
int * score_ptr{score}
for(int i{0} ; i<3 ; i++){
cout << ptr << endl;
cout << *ptr << endl;
ptr++; // 指针移动指向下一个元素
}
|
2. 指针递减
指针递减 , 如果是数组操作,可以指向前一个元素
1
2
3
4
5
6
7
8
9
10
11
12 | int score[]{90,85,100};
int * score_ptr;
score_ptr = score[2]; //指针指向的是数组最后一个元素内存地址
for(int i{3} ; i>0 ; i--){
cout << ptr << endl;
cout << *ptr << endl;
ptr--; // 指针移动指向下一个元素
}
|
3. 等价判断
两个指针的等价判断,实际上是他们指向的地址比较
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | #include<iostream>
using namespace std;
int main(){
string s1{"张三"};
string s2{"张三"};
string *p1 {&s1};
string *p2 {&s2};
string *p3 {&s1};
cout << (p1 == p2) <<endl; //false
cout << (p1 == p3) <<endl; //true
cout << (*p1 == *p2) <<endl; //true
cout << (*p1 == *p3) <<endl; //true
return 0 ;
}
|
7. 指针与常量
1. 指针常量
const int *p
表示指针指向常量 , 不允许修改对应的值,但是可以指向别的地方, 这和常量修改值一样。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | int main(){
//定义高分和低分的变量
int high_score{100};
int low_score{75};
//使用指针常量指向高分。这里的const修饰的是指向的数据
const int *score_ptr {&high_socre};
//不允许修改值,因为此时编译器会认为high_score 是一个常量
*score_ptr = 86 ; // 错误
//可以指向其他位置。
score_ptr = &low_score ; // 正确
//当然也可以通过变量方式来修改值。
high_score = 88;
return 0 ;
}
|
2. 常量指针
int* const p
表示这个指针是常量指针,不能再指向别的地方了,但是可以修改目前指向地方的值
1
2
3
4
5
6
7
8
9
10
11
12
13 | int main(){
int high_score{100};
int low_score{75};
//表示这个指针是一个常量,这里的const修饰的是指针
int *const score_ptr{&high_score};
//允许修改值,但是不允许再做其他指向
*score_ptr = 86 ; // 正确
score_ptr = &low_score ; // 错误
return 0 ;
}
|
3. 常量指针指向常量
const int* const p
表示这个指针是常量指针,并且它指向的位置的值也是常量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | int main(){
int high_score{100};
int low_score{75}l
//第一个const是修饰指向的数据,第二个const是修饰指针。
//表示不管是指针还是指向的数据,都是常量
const int *const score_ptr{&high_score};
//既不允许修改指向,也不允许修改指向的值
*score_ptr = 86 ; // 错误
score_ptr = &low_score ; // 错误
return 0 ;
}
|