指针其实就是一块地址,可以用来保存我们想要访问或者修改的变量地址,当我们需要访问或者修改保存的地址时,直接通过指针变量去操作就可以了。
定义指针的语法也很简单:数据类型 *指针变量名;
,比如定义一个整型指针变量: int* p;
,假设我们要为指针变量赋值,我们需要用的取地址符&
,如下面代码所示:
int a = 10;
int* p;
p = &a;
如上面代码所示,我们定义了一个变量a,一个指针变量p,并且把a的地址保存到指针变量p中。
我们定义了指针变量并且赋值后应该如何读取指针变量保存的地址对应的值呢?也就是假设我们定义了一个整型变量a,赋值为10,一个指针变量p,将变量a的地址赋给p,这时我们应该如何去通过指针p访问修改a的值呢?这时就需要用到指针的解引用了,需要用到符号*
,代码如下所示:
#include
using namespace std;
int main() {
int a = 10;
// 定义指针语法:数据类型 *指针变量名;
int* p;
// 让指针记录变量a的地址
p = &a;
// 2.使用指针
cout << "a的值未被修改前:" << a << endl;
*p = 100;
cout << "a的地址: " << (int) & a << endl;
cout << "指针p为:" << p << endl;
cout << "指针指向的值为:a= "<<a<< " ,*p = " << *p << endl;
system("pause");
return 0;
}
运行结果:
如上面代码所示,为了对比,我们打印出了变量a之前的值,并且我们打印出了指针p的值,可以看到指针p是一个地址,然后是使用*p = 100
的方式访问并修改了指针变量中保存的地址对应的值,而指针变量p中保存的地址正是a的地址,所以修改的其实是a的值。所以a的值变成了100;
注意:可以通过解引用的方式来找到指针指向的内存,*p的方式就是解引用,这样可以访问指针变量中保存的地址对应的值
空指针就是指针变量指向的内存编号为0的空间,我们可以使用空指针来初始化指针变量,比如:
int *p = NULL;
,这里需要注意的是,空指针指向的内存是不可以被访问的,例如:
int *p = NULL;
*p = 100;// 错误
上面的代码会导致程序报空指针异常:
这是因为空指针指向的内存是不可以访问的,0~255之间的内存是系统占用的,不可以访问,访问的话就会报异常。
野指针就是指针变量指向非法的内存空间,例如我们定义了一个指针变量:int* p = (int*)0x1100;
,而0x1100这个地址我们不知道是谁的,就胡乱指的,这种情况特别危险,假设这块地址有重要数据,我们随便一指,然后一改,就会导致重要数据丢失,所以野指针需要被避免。假设我们强制访问未知内存,则会报异常:
所以我们尽量去访问自己申请的空间,空指针和野指针都不是我们申请的空间,因此不要访问
现在我们知道了指针其实就是一块地址,它用于保存操作其他变量的地址,简单说它就是用来保存地址的,所以它占的空间不需要太大,根据操作系统的位数不同,指针占的空间也不一样,在32位操作系统上,指针占4个字节,在64位操作系统上,指针占8个字节,所有的类型都一样,不管是整型指针,还是浮点型指针,都是一样的,验证的代码如下所示。
#include
using namespace std;
int main() {
// 指针所占内存空间,32位操作系统下,占用4个字节。64位下占用8个字节
cout << "size of (int*)" << sizeof(int *) << endl;
cout << "size of (float*)" << sizeof(float *) << endl;
cout << "size of (double*)" << sizeof(double *) << endl;
cout << "size of (char*)" << sizeof(char*) << endl;
system("pause");
return 0;
}
运行结果:
如上面代码和运行结果可知,各个类型的指针都是占8个字节,因为我的操作系统是64位的。读者也可以验证下自己的操作系统下指针占多上字节。
我们都知道,const是表示常量的意思,在C++中,被这个关键字修饰的变量是不可以被修改的,和Java的final关键字一样,因为指针可以访问并修改内存中的值,这样可能会引起安全问题,所以可以使用const关键字控制指针对内存的修改。const修饰指针主要有三种:
const修饰指针,称为常量指针,例如:const int *p;
,常量指针的特点就是,指针指向的值不可以被修改,但是指针的指向可以修改,如下代码所示:
#include
using namespace std;
int main() {
// const 修饰指针,常量指针
int a = 10;
int b = 10;
const int* p = &a;
// 指针指向的值不可以改,指针的指向可以改
*p = 20;// 错误,常量指针指向的值不可以修改,编译会报错
p = &b;//正确,常量指针指向的值可以修改
system("pause");
return 0;
}
如果强制修改无法编译通过
const修饰常量,称为指针常量,例如:int * const p;
,指针常量的特点就是:指针的指向不可以改,指针指向的值可以改,如下面代码所示:
#include
using namespace std;
int main() {
int a = 10;
int b = 10;
int* const p2 = &a;
*p2 = 20; // 正确,指针常量指向的值可以修改
p2 = &b; // 错误 指针常量的指向不可以改,编译会报错
system("pause");
return 0;
}
当const既修饰指针也修饰常量的时候,例如: const int * const p;
,这时候指针的指向和值都不允许修改。如下代码所示:
#include
using namespace std;
int main() {
int a = 10;
int b = 10;
const int* const p3 = &a;
*p3 = 100;// 错误
p3 = &b; // 错误
system("pause");
return 0;
}
指针其实也可以用来访问数组,遍历数组,在C++中,数组名称就是数组的首地址,而数组的存储是连续的,所以我们只要把这个首地址给到指针变量,就可以操作遍历这个数组了,如下面代码所示:
#include
using namespace std;
int main() {
// 利用指针访问数组中的元素
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
cout << "first Element:" << arr[0] << endl;
int* p = arr;//数组名就是数组的首地址
cout << "指针访问第一个元素:" << *p << endl;
p++; // 让指针向后偏移一个元素所占的字节数
cout << "指针访问第二个元素: " << *p << endl;
cout << "利用指针遍历数组" << endl;
int* p2 = arr;
for (int i = 0; i < 10; i++) {
cout << *p2 << endl;
p2++;
}
system("pause");
return 0;
}
运行结果:
指针可以用来做函数的参数,这样可以实现真正的操作传进来的参数原始实参的值,我们给函数传递参数时有两种方式,一种是值传递,传递过去的是值,这个值的修改不会影响原始的实际值,另一种方式是地址传递,这种传递方式传递的是地址,假设我们修改了这个地址对应的值,那么就会影响原始变量的值。举个例子,实现两个数交换,我们使用值传递的方式代码如下:
#include
using namespace std;
void swap(int a, int b) {
int tmp = a;
a = b;
b = tmp;
cout << "swap: a= " << a << endl;
cout << "swap: b= " << b << endl;
}
int main() {
// 指针作为函数参数
// 1、值传递
int a = 10;
int b = 20;
swap(a, b);
// 实参没有改变
cout << "a= " << a << endl;
cout << "b= " << b << endl;
system("pause");
return 0;
}
运行结果:
我们可以看到,虽然在swap函数种的值已经交换了,但是我们的原始变量a,b的值还是没有变化。我们使用地址传递的方式,代码如下:
#include
using namespace std;
void swap1(int* p1, int* p2) {
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
int main() {
int a = 10;
int b = 20;
// 地址传递: 可以修改实参
swap1(&a, &b);
cout << "地址传递:a= " << a << endl;
cout << "地址传递:b= " << b << endl;
system("pause");
return 0;
}
运行结果:
如上面的代码所示,我们传递参数的时候将参数的地址传递给函数,在swap1函数中解析出地址对应的值并且做交换,这样就能修改原始实参的值了。所以指针做函数参数其实就是一种地址传递。
学习完指针,我们用指针做一个冒泡排序的算法。代码如下所示:
#include
using namespace std;
// 参数1:数组首地址,参数2:数组长度
void bubbleSort(int* arr, int len) {
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - i - 1; j++) {
// 如果j>j+1的值,就交换
if(arr[j] > arr[j + 1]){
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
void printArr(int *arr,int len) {
for (int i = 0; i < len; i++) {
cout << arr[i] << endl;
}
}
int main() {
// 利用冒泡排序,对整型数组进行升序排序
// 1.先创建数组
int arr[10] = { 1,3,2,5,7,1,9,10,7,13 };
// 2.创建函数,实现冒泡排序
int len = sizeof(arr) / sizeof(arr[0]);
bubbleSort(arr, len);
// 3.打印结果
printArr(arr,len);
system("pause");
return 0;
}
运行结果: