C++的指针


指针

指针的定义

指针存放的是地址。
刚刚看到一个场景,阐述了为什么会存在指针

  • 当把数据传入一个函数,函数的功能是打印信息,而输入的信息有几百万条
  • 如果没有指针,那么值传递就需要再复制一份这几百万条输出,对时间、空间的开销都是很大的
  • 故而地址传递是很有效的一种解决方式
int * a;	// 这里是指向各种类型的指针
float * b;
char * c;
double * d;
bool * e;
short * f;
long * g;
long long * h;
string * str;	// str指向标准库提供的string类
type * t;	// 指向某自定义类型type的指针t

指针的大小

根据编译器、操作系统的位数决定
一般是32位或是64位编译器、操作系统
不论是指向什么类型的指针,其大小都是4或8个字节(对应32、64位操作系统)

原因

cpu、操作系统的位数决定了其可以寻址的空间的大小。
这也决定了计算机最大内存空间的大小,例如:32位机最大内存为4GB(2的32次方:4 * 2^30 -> 4GB)
因为32、64位cpu一次最大能处理的二进制数长度就是32、64位,一个地址由这么长的二进制去描述,故而最大的寻址空间就是由其限制的。

那为什么指针还要定义类型呢?

笔者也是初学C++,通过学习其他语言的浅见...
自认为是以下原因(也可能是错误的)

  1. 指针的值指向了某一段地址空间的首地址,即第一个字节。
  2. 指针的类型告诉编译器,其后多少个字节是这个变量的值。
    例如:一个int类型的指针 int * p; 它指向0x01这个地址,由于其是指向int类型,而该类型的大小是4个字节,所以从0x01 ~ 0x04这四个字节都是指向的变量存储的地址空间

指针的使用

需要用到取地址符&和解引用操作符*

int a = 53;
int * p = &a;	// 取a的地址赋值给p

cout << *p << endl;	// 解引用指针p,得到p指向的地址空间中的值,结果是输出53.

*p = 217;	// *p拿到的就是该地址空间的使用资格,可以取值也可以修改(后面介绍的常量指针和指针常量会限制修改的权限)
cout << a << endl;	// 上面就是通过p修改了变量a所指向地址空间的值,再输出,a就被修改为217,输出为217。

空指针和野指针

空指针即指针指向为空,可以在还没有决定指针指向之前,用来初始化指针。

int * p = NULL;	// 指针初始化为NULL,空指针
cout << p << endl; // 在acwing提供的编译器上,输出的值即为0

int * p1;
cout << p1 << endl; // 只定义,不初始化,输出的值也为0

野指针即指向非法空间的指针(非法空间指没有定义、没有权限去操作的地址空间?)

int * p = (int *)0x1100;	// 定义了一个野指针指向了地址空间为0x1100的地址,但是该地址没有被允许此时访问,故而p为一个野指针
cout << *p << endl;	// 此时编译可以通过,但是程序会崩溃

const修饰的常量指针、指针常量

1. 常量指针

  • 可以修改指针指向
  • 不可以修改指针指向的地址空间的值
int a = 53;
int b = 217;
const int * p = &a;

// 下面两个操作
p = &b;	// 正确,常量指针可以修改指针指向,这里p从指向a变为指向b
*p = 100;	// 错误,常量指针为只读,不可以修改其指向的地址空间

2. 指针常量

  • 可以修改指向的地址空间的值
  • 不可以修改指向
int a = 53;
int b = 217;
int * const p = &a;

// 下面两个操作
p = &b; // 错误
*p = 100; // 正确

如何记忆?
直接根据操作符顺序去念和记忆:const 为常量,* 为指针

  • const int * p 即为常量指针
  • int * const p 即为指针常量

3. 同时修饰常量、指针的情况: 此时既不可以修改指针指向也不可以修改指向地址空间的值。

int a = 53;
const int * const p = &a;	// 此时p不可以改变指向、也不可以通过解引用操作符去操作指向的地址空间

指针与数组

数组名不同于变量名,其就是整个数组空间的首地址,不需要取地址符
除了通过数组的下标,我们也可以通过指针去遍历操作数组空间中的值

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

for (int i = 0; i < 10; i ++)
{
	cout << *p << endl;
	p ++;	// 指针自增,即在地址空间向下偏移一个其指向类型大小的距离
}

经过在acwing的编译器上测试

  • 指针变量自增操作会使其指向的地址偏移一个该类型长度的距离,如int型指针就是地址+4,double型指针就是地址值+8
  • 指针变量加上一个整形的常量,会偏移常量个该类型长度的距离,如:p + 2,如果是int型,那么新地址为:原地址+8,如果是double:原地址+16
  • 计算机操作最小的单位就是字节,上面所说的跨越距离,就是一个个字节。

指针与函数

指针可以实现函数形参与实参的地址传递,从而通过地址修改实参指向地址空间的值
值传递

void swap(int a, int b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

int main()
{
	int a = 53;
	int b = 217;
	
	swap(a, b);	// 值传递
	
	cout << "a:" << a << endl;
	cout << "b:" << b << endl;
	
	return 0;
}

地址传递

void swap(int *pa, int *pb)
{
	int tmp = *pa;
	*pa = *pb;
	*b = tmp;
}

int main()
{
	int a = 53;
	int b = 217;
	
	swap(&a, &b);	// 地址传递
	
	cout << "a:" << a << endl;
	cout << "b:" << b << endl;
	
	return 0;
}

值传递和地址传递是很灵活的使用的。
在算法题、工程项目中根据不同情况去具体的使用。

综合指针、数组、函数

实现了一个通过地址传递的冒泡排序、以及遍历输出排序后的数组的函数

#include
using namespace std;

// 冒泡排序
void bubbleSort(int * arr, int len)
{
    for (int i = 0; i < len - 1; i ++)
    { for (int j = 0; j < len - 1 - i; j ++)
     {
         if (arr[j] > arr[j + 1])
         {
             int tmp = arr[j];
             arr[j] = arr[j + 1];
             arr[j + 1] = tmp;
         }
     }
    }
}
// 遍历输出排序后的数组
void printArray(int * arr, int len)
{
    for (int i = 0; i < len - 1; i ++)
    {
        cout << arr[i] << endl;
    }
}

int main()
{   
    int arr[10];
    
    for (int i = 0; i < 10; i ++) {
        int x;
        cin >> x;
        arr[i] = x;
    }   
	
    int len = (sizeof arr) / (sizeof arr[0]);
    
    bubbleSort(arr, len);
    
    printArray(arr, len);
    
    return 0;
    
}
C++