C++中的指针和引用

学习C++中指针和引用时的笔记

指针的基础用法

指针的类型

指针是一个4或8byte(取决于编译器)的变量,它能保存内存中某一个byte的地址。

变量或者对象在内存中存放时会占用多个byte,所以指针指向的是变量或者对象起始的那个byte的位置

image-20220729021026087

指针也是有类型的

要操作指针所指向的内容时,会同时操作一片的内存。而指针的类型决定了要操作的内存的大小。

可以用<类型名> *描述指针的类型,指向<type>类型的变量或数组的指针类型都为<type> *

指针也是有地址的。

指针是一种特殊的变量,它首先是个变量,所以它本身是有地址的。

变量和类型.md中有查看指针类型的代码

定义指针

<type> * <name>

这样就能定义一个<type> *类型的指针

假设T是一种类型,可以这么定义一个类型为T *的指针

1
T *p;

取值/取址符

当对一个指针使用取值符号(例如*p)时,读取内存中从p开始的若干个字节作为类型T

取的字节数取决于指针类型的大小sizeof(T)

1
2
3
4
// sizeof(T) = 4
T data; //变量
T *p = &data; //指针
T arr[N]; //数组
名称 类型 大小
p(指针)0 T * 4或8 byte
&data(取址符) T * 4或8 byte
*p(取值符) T sizeof(T)
p[n],等同于*(p+n) T sizeof(T)
arr(数组) T [N] sizeof(T)*N(特殊!)
&arr T (*) [N] 4或8 byte
arr+0(对数组整数操作) T * 4或8 byte

如果指针指向的供使用的内存空间大小指针类型大小相同,则能正常工作。若不同,则操作指针时会改变其它位置的内存,可能改动其它变量直接崩溃

注意区分:

  • 在定义变量时,"*“跟着后面的变量名表示后面的变量为指针
  • 在表示类型时,"*“表示这是一个指针
  • 在表达式中,"*“用作取值运算符
  • 变量和类型.md中有进一步的描述

总结:

  • *取值符号:取指针指向的变量,能被赋值(等号左右都能放)
  • &取址符号:取变量的内存地址
  • []下标运算符:根据指针和下标取变量

运算

不同类型的指针虽然存放的都是内存地址,但不能互相赋值和比较,必须经过强制类型转换。

NULL可以被赋值给所有指针,其值=0,表示空指针

指针p加减整数n,表示将指针p向前或向后移动n * sizeof(T)。可以用自增或者自减。

image-20220719214715548

指针特殊的用法

void类型指针

可以定义void类型的指针。void表示无类型,下面的语句意思是定义一个没有说明类型的指针。

void指针可以被任何类型的指针赋值不能直接赋值给其它类型的指针

1
void * p;

此时,sizeof(p)=4,由于void不表示具体类型,sizeof(void)未定义,对p取值时不知道要操作多大的内存空间,读取值后也不知道转换为声明类型。所以对于void类型的指针类似于下面的操作是不合法的:

1
2
*p //类型是void,无具体类型,不合法
p+1 //sizeof(void)没定义,无法移向下一个位置,不合法

指针的嵌套

可以有int **p;这样的语句,表示指向指针的指针。甚至可以有int *****p这种东西。

1
2
3
4
int num=233;
int *p=&num;
int **pp=&p;
cout<<**pp<<endl;

以上程序可以输出233

1
2
flowchart LR
	pp["pp(&p)"] --->p["p(&num)"]--->num["num(233)"]

函数指针

函数的代码在程序运行的时候被放在了内存的某个位置。可以定义函数指针并将其指向某个函数,然后通过函数指针调用被指向的函数

1
2
3
4
5
6
7
int max(int a,int b){return a>b?a:b;}
int main()
{
    int (* func)(int,int); //前面的括号必须有
    func=max; //函数名可以直接做指针
    cout<<func(2,3)<<endl;
}

变量和类型.md中详细描述了“复杂类型声明”问题。

对象的指针

1
2
3
4
5
6
7
class T
{
    public:
        int a,b;
};
T data;
T * p = &data;

若要读取p指向的class内的元素的值,有两种方法:

1
2
p->a;
(*p).a;//有优先级,不能省括号

const与指针

1
2
3
const int *p;
int const *p;
int *const p = &num;

前面两个表示p指向的是一个int类型的常量,即*p无法被赋值。

最后一个表示p是一个常量,即p存放的地址无法被修改。

数组和指针

疑问

arr==&arr编译错误,提示:

1
[Error] comparison between distinct pointer types `int*' and `int (*)[100]' lacks a cast`

感觉数组和指针还是有一点区别的?

运行以下代码:

1
2
3
4
	arr[1]=1;
	cout<<arr[1]<<endl;
	cout<<(&arr)[1]<<endl;
	cout<<p[1]<<endl;

输出:

1
2
3
1
0x64ff20
1

再运行以下代码

1
2
3
	cout<<(&arr)[0]<<endl;
	cout<<(&arr)[1]<<endl;
	cout<<(&arr)[2]<<endl;

输出:

1
2
3
0x64fd90
0x64ff20
0x6500b0

根据编译器提示,

1
2
3
	// arr	int*
	// &arr	int(*)[100]
	// *p	int*

int(*)[100]是什么?为什么对arr取地址会得到这个?怎么方便的获得一个东西的类型?

*&到底修饰的是什么东西?

指针++又是什么意思?对指针加减是按字节修改还是按类型大小修改?

指针也有类型?指针的类型有什么用?怎么做类型转换?

对一个数组,指针的类型包不包括那个[100]

sizeof()对变量、数组、指针的区别?

过了一年我意识到,产生这些疑问,本质上是对C/C++的类型描述不清楚。

有关类型

变量和类型.md中详细描述了“复杂类型声明”问题。这里举几个简单的例子:

  • nxm的二维数组a,元素类型为TT [n][m]
  • 对数组a取址 T (*) [n][m]
  • 直接对a进行指针操作 T * [m]
  • nxm的二维数组b,元素类型为T *T * [n][m]
  • 对数组b取址 T * (*) [n][m]
  • 直接对b进行指针操作 T * * [m]

对数组名的几种操作

  • <name>:直接对数组名进行sizeof等操作,求的是数组实际的大小
  • <name>:对数组名进行任何指针操作,将进行自动类型转换,等同于操作指向数组首元素的指针(&<name>[0]),同时维度-1
    • 一位数组:数组类型<type> [n]=>自动转换为<type> *(首元素为单个变量,指向首元素的为普通指针)
    • 多维数组:数组类型<type> [n1][n2][nk]=>自动转换为<type> (*) [n2][nk](首元素为数组,指向首元素的为数组指针)
  • <name>[k]:取数组第k位,实际为指针后移k位取值(*(<name>+k)),同时维度-1
    • 一维数组:<type> [n]=><type>
    • 多维数组:<type> [n1][n2][nk]=><type> [n2][nk]
  • &<name>:取指向数组的指针(<type> (*) [n1][n2][nk])

数组作为函数参数

要向函数传递数组,可以有以下等价写法

1
2
3
4
5
6
void func(int * arr);
void func(int arr[]);
void func(int arr[10]);

int arr[10];
func(arr);

这三种方式是等价的,编译器认为这都是int *类型的指针。

调用函数时只会将arr作为指针传递给func。函数内只能将arr参数作为指针来处理

函数声明中数组大小完全没有作用。

可以通过sizeof(arr)来验证,其值为4或8。

引用

概念

引用相当于变量的别名,是指针的语法糖。

在初始化之后不能修改引用的指向

定义引用:<类型名称> & <变量名>

1
2
3
int num = 233;
int & r = num; // r的类型为 int &
cout<<r<<endl; // 输出233

例子:

1
2
3
4
5
6
void swap(int & a,int & b)
{
    int tmp=a;
    a=b;
    b=tmp;
}

作为函数类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
int n = 4;
int & setvalue()
{
    return n;
}
int main()
{
    setvalue() = 40;
    cout<<n<<endl;
    return 0
}

输出40

在上面的例子中,由于函数返回n,可以将setvalue()整个看作n的引用,也就是n=40

引用作为函数类型时不能返回函数内的局部变量。因为函数返回后,函数内的局部变量已经失效,其引用指向不确定的内存,会造成错误。

同理,指针也是这样,返回指针的时候,不能指向局部临时变量,否则指针将变为野指针