一、前言
C++作为面向对象编程语言,其与传统的过程性编程的区别在于OOP强调的是在程序运行阶段进行决策而不是在编译阶段。运行阶段决策提供了灵活性,可以根据当时程序的执行情况对变量存储空间进行调整。
以为数组分配内存为例。传统的方法是在声明数组时就指定数组的长度,因此数组的长度在程序编译时就已经设定好了,这就是编译阶段决策。比如说先声明一个包含200个元素的数组,但是在实际使用的时候只用到了20个,那么此时程序便浪费了内存。OOP通过将这样的决策推迟到运行阶段进行使程序更加灵活。也就是说在使用OOP时可以在运行阶段确定数组的长度,而C++采用的方法是使用new来请求适当的内存空间同时使用指针来跟踪新分配的内存的地址。
二、声明和初始化指针
指针其实就是地址,下面是一个初始化指针的例子:
#include<iostream>
using namespace std;
int main(){
int a = 123;
int* p = &a; //int*指的是*p(*p表示存储在地址p处的值)的类型为int,也就是说指针p所指向的是int型变量。这里&a可以理解为取a的地址。
cout<<"a的存储地址是:"<<p<<endl;
cout<<"地址p处存储的值是:"<<*p<<endl;
return 0;
}
上述例子输出结果为:
a的存储地址是:0x61fe14
地址p处存储的值是:123
三、指针与数组
用指针来表示数组也是十分常见的。我们将整数变量加1后其值也会增加1,但是将指针变量加1后其增加的量等于它所指向的数据类型的字节数(这也证明了指针所表示的地址并不是一个数)。
#include<iostream>
using namespace std;
int main(){
int num[4] = {1,2,3,4};
int* addr1 = &num[0]; //指针初始化法1
int* addr2 = num; //指针初始化法2,也可以看出数组的第一个元素的地址可直接由数组名得出,而整个数组的地址可以用&num得出
//不能用int* addr1,addr2;因为这样只有addr1是指针而addr2是int型变量
cout<<"用法1得到的指针addr1的值是:"<<addr1<<endl;
cout<<"用法2得到的指针addr2的值是:"<<addr2<<endl;
//用指针获取数组中的元素
cout<<"存储地址:"<<addr1<<" 元素:"<<*addr1<<endl;
cout<<"存储地址:"<<addr1+1<<" 元素:"<<*(addr1+1)<<endl;
cout<<"存储地址:"<<addr1+2<<" 元素:"<<*(addr1+2)<<endl;
cout<<"存储地址:"<<addr1+3<<" 元素:"<<*(addr1+3)<<endl;
return 0;
}
用指针来表示数组我的理解为是将数组的第一个元素的地址存储在指针中,因此后面在访问数组元素的时候可以对指针进行运算,表面看是+1实际上是+4(即一个int型变量占4字节)。
输出结果:
用法1得到的指针addr1的值是:0x61fe00
用法2得到的指针addr2的值是:0x61fe00
存储地址:0x61fe00 元素:1
存储地址:0x61fe04 元素:2
存储地址:0x61fe08 元素:3
存储地址:0x61fe0c 元素:4
四、指针与字符串
用指针来表示字符串和用指针表示数组类似。在cout和多数C++表达式中,char数组名、char指针以及用引号括起来的字符串常量都被解释为字符串第一个字符的地址。与数组不同之处在于字符串本身和其第一个字符的存储地址之间存在一定的联系。一般来说编译器在内存中会留出一些空间,以存储程序源代码中所有用引号括起来的字符串,并将每个被存储的字符串与其地址关联起来。因此可直接使用字符串本身来表示第一个字符的地址。
#include<iostream>
#include<cstring>
using namespace std;
int main(){
char myList[20] = "hello world";
char* p1 = &myList[0]; //初始化法1
char* p2 = myList; //初始化法2
char* p3 = "hello world"; //初始化法3
cout<<"p1:"<<p1<<endl;
cout<<"p2:"<<p2<<endl;
cout<<"p3:"<<p3<<endl;
return 0;
}
输出结果:
p1:hello world
p2:hello world
p3:hello world
由初始化法3和输出结果来看,字符串本身和其第一个字符的存储地址间确实存在联系。一般来说,如果给cout提供一个指针,它将打印地址。但是如果指针的类型是char*,则cout将显示指向的字符串。那么我们如何得到第一个字符的存储地址呢?如果要显示字符串的地址,则必须将这种指针强制转换为另一种指针类型,比如int*。
#include<iostream>
#include<cstring>
using namespace std;
int main(){
char myList[20] = "hello world";
char* p1 = &myList[0]; //初始化法1
char* p2 = myList; //初始化法2
char* p3 = "hello world"; //初始化法3
cout<<"p1地址:"<<(int*)p1<<endl;
cout<<"p2地址:"<<(int*)p2<<endl;
cout<<"p3地址:"<<(int*)p3<<endl;
return 0;
}
输出结果:
p1地址:0x61fdf0
p2地址:0x61fdf0
p3地址:0x404001
tip:这里p3地址为啥和上面两个不同还需要我继续学习。。
字符串获取指定位置的字符和数组方法相同。
#include<iostream>
#include<cstring>
using namespace std;
int main(){
char myList[20] = "hello world";
char* p1 = &myList[0]; //初始化法1
char* p2 = myList; //初始化法2
char* p3 = "hello world"; //初始化法3
cout<<"第一个字符:"<<*p1<<endl;
cout<<"第二个字符:"<<*(p1+1)<<endl;
cout<<"第三个字符:"<<*(p1+2)<<endl;
cout<<"第四个字符:"<<*(p1+3)<<endl;
cout<<"第五个字符:"<<*(p1+4)<<endl;
cout<<"整个字符串:"<<p1<<endl;
return 0;
}
输出结果:
第一个字符:h
第二个字符:e
第三个字符:l
第四个字符:l
第五个字符:o
整个字符串:hello world
五、指针的危险之处
C++中创建指针时,计算机将分配用来存储地址的内存,但不会分配用来存储指针所指向的数据的内存。
int* p;
*p = 123;
上述代码中,p确实是一个指针,但是并没有将任何地址赋值给他,也就是说计算机为指针p提供了内存用来存储p所指向的地址,但是当前这块内存中我们没有存放内容(不是真的没有内容而是可能是任何一个地址反正不是我们指定的地址)。因此下面为指针p所指向的地址赋值时计算机会把123赋值到哪块地址是随机的,我们并不知道(有可能是存放源代码的地址,所以很危险)。通常这种bug很隐蔽,很不好跟踪。