C++中指针知识总结

1 指针的基本概念

1.1 变量的地址

变量是内存变量的简称,在C++中,每定义一个变量,系统就会给变量分配一块内存,内存是有地址的。

#include 
using namespace std;

int main()
{
	int a;
	char b;
	float c;
	string d;

	cout << "a的地址:" << (long long)&a << endl;
	cout << "b的地址:" << (long long)&b << endl;
	cout << "c的地址:" << (long long)&c << endl;
	cout << "d的地址:" << (long long)&d << endl;

	return 0;
}

1.2 指针变量

指针变量简称指针,它是一种特殊的变量,专用于存放变量在内存中的起始地址。
语法:**数据类型 *变量名;

数据类型必须是合法的C++数据类型(int、char、double或者其他自定义的数据类型)
星号表示这个变量是指针。

1.3 对指针赋值

不管是整型、浮点型、字符型,还是其他的数据类型的变量,他的地址都是一个十六进制数。我们用整型指针存放整型变量的地址;用字符型指针存放字符型变量的地址;用浮点型指针存放浮点型变量的地址。
语法:指针=&变量名

1.4 指针占用的内存

指针也是变量,是变量就要占用内存空间。

在64位操作系统中,不管是什么类型的指针,占用的内存都是8字节。

	int a=10;
	char b='a';
	float c=1.2;
	string d="hello";

	int* pa = &a;
	char* pb = &b;
	float* pc = &c;
	string* pd = &d;

以整型变量a为例:

  • a是整型变量
  • &a是整型变量a的地址
  • pa也是整型变量a的地址
  • *pa是整型变量a的值

2 使用指针

申明指针变量后,在没有赋值之前,里面是乱七八糟的值,这时候不能使用指针。
指针存放变量的地址,因此,指针名表示的是地址(就像变量名可以表示变量的值一样)
*运算符被称为间接值或者解除引用运算符,将它用于指针,可以得到该地址的内存中存放的值。

多个指针可以指向同一个内存。

操作任意一个指针,改变内存中的值,其他指向该内存的变量的值都会同时改变。例如:

int a=10;
char b='a';
float c=1.2;
string d="hello";

cout << "a的值:" << a << endl;
cout << "b的值:" << b << endl;
cout << "c的值:" << c << endl;
cout << "d的值:" << d << endl;

cout << "*pa的值:" << *pa << endl;
cout << "*pb的值:" << *pb << endl;
cout << "*pc的值:" << *pc << endl;
cout << "*pd的值:" << *pd << endl;

*pa = 10;
*pb = 'A';
*pc = 1.3;
*pd = "world";

cout << "a的值:" << a << endl;
cout << "b的值:" << b << endl;
cout << "c的值:" << c << endl;
cout << "d的值:" << d << endl;

cout << "*pa的值:" << *pa << endl;
cout << "*pb的值:" << *pb << endl;
cout << "*pc的值:" << *pc << endl;
cout << "*pd的值:" << *pd << endl;
a的值:10
b的值:a
c的值:1.2
d的值:hello
*pa的值:10
*pb的值:a
*pc的值:1.2
*pd的值:hello

a的值:10
b的值:A
c的值:1.3
d的值:world
*pa的值:10
*pb的值:A
*pc的值:1.3
*pd的值:world

通过上述可以发现,操作指针*p可以改变a的值。(这里面操作的其实是内存地址,也就是说改变了内存地址中的值,所以指向该内存的变量都变了)

就好比一个瓶子装的矿泉水,不同人给他命名不同,如瓶A、瓶B、瓶C,那么在各自的瓶子中,瓶子装的都是矿泉水,现在不管是谁,瓶子里的水换成饮料,那个在各自命名的瓶子中装的都变成了饮料。也就是,操作的是瓶子内的液体,并不是各自瓶子的命名。

程序在存储数据的时候,必须跟踪的三种基本属性:

  • 数据存储在哪里;
  • 数据是什么类型;
  • 数据的值是多少。

申明一个指针变量,存储的值是地址,而不是值本身。程序直接访问该内存单元。

3 指针用于函数的参数

指针存放了变量的地址,通过指针操作变量和直接操作变量名的效果是一样的。

如果把函数的形参声明为指针,调用的时候把实参的地址传进去,形参中存放的是实参的地址。在函数中通过解引用的方法直接操作内存中的数据,可以修改实数的值,这种方法被通俗的称为地址传递或者传地址

值传递:函数的形参是普通变量。

传地址的意义如下:

  • 可以在函数中修改实参的值
  • 减少内存拷贝,提升性能
#include 
using namespace std;

void sendMessage(int* a, string* b)
{
	cout << "我是第" << *a << "号:" << *b << endl;
	*a =5;
	*b = "我是一只小小鸟";
}

int main()
{
	int no=2;
	string message="我是一只傻傻鸟";
	sendMessage(&no, &message);

	cout << "我是第" << no << "号:" << message << endl;

	return 0;
}

4 用const修饰指针

4.1 常量指针

语法:const 数据类型* 变量名;

不能通过解引用的方法修饰内存地址中的值。(用原始的变量名可以修改)

(1)指向的变量(对象)可以改变(之前是指向变量a的,后来可以改为指向变量b)

int a = 10, b = 8;
const int* p = &a;

cout << "a = " << a << " *p = " << *p << endl;
cout << "a的地址是:" << &a << " *p的地址是:" << p << endl;
//*p = 15; // error,常量指针不能通过解引用的方法修改内存地址中的值,但是可以指向其他变量b
p = &b;
cout << "b = " << b << " *p = " << *p << endl;
cout << "b的地址是:" << &b << " *p的地址是:" << p << endl;

返回值:

a = 10 *p = 10
a的地址是:010FFD50 *p的地址是:010FFD50
b = 8 *p = 8
b的地址是:010FFD44 *p的地址是:010FFD44

也就是指针p指向另外的变量时,它的地址就不再是上一个了。而是指向新的变量的地址内存。

(2)一般用于修饰函数的形参,表示不希望在函数中修改内存地址中的值。
(3)如果用于形参,虽然指向的对象可以改变,但是这么做没有任何意义。
(4)如果形参的值不需要改变,建议加上const修饰,程序可读性更好。

4.2 指针常量

语法:数据类型 const 变量名;*
指向的变量(对象)不可改变。

注意:

  • 在定义的同时必须初始化,否则没有意义。
  • 可以用过解引用的方法修改内存地址中的值。
int* const ptr=&a;
*ptr = 20;
cout << "a = " << a << " *ptr = " << *ptr << endl;

// ptr = &b;  //error 指针常量指向的变量不可以改变,但是可以通过解引用修改内存地址的值

返回值:

a = 20 *ptr = 20

可以看出,通过*ptr可以修改a的内存地址的值,也就是a的值也发生了改变。

  • C++编译器把指针常量做了一些特别的处理,改头换面之后,有一个新名字,叫引用。本质还是指针常量。

4.3 常指针常量

语法:const 数据类型* const 变量名;
指向的变量(对象)不可以改变,也不能通过解引用的方法修改内存地址中的值。

常引用。

4.4 总结

常量指针:指针指向可以改,指针指向的值不可以更改。(const 数据类型* 变量名)
指针常量:指针指向不可以更改,指针指向的值可以更改。(数据类型* const 变量名)
常指针常量:指针指向不可以更改,指针指向的值也不可以更改。(const 数据类型* const 变量名)

5 void*指针

在C++中,void表示为无类型,主要有三个用途:

  • 函数的返回值用void,表示函数没有返回值。
void func(int a,int b)
{
	// 函数体代码
	return;
}
  • 函数的参数填void,表示函数不需要参数(或者让参数列表空着)
int func(void)
{
	// 函数体代码
	return 0;
}
  • 函数的形参用void *,表示接受任意数据类型的指针。
#include
using namespace std;

void func(string varname, void* p)
{
	cout << varname << "的地址是:" << p << endl;
}

int main()
{
	int a;
	char b;

	cout << "a的地址是:" << &a << endl;
	cout << "b的地址是:" << &b << endl;
	func("a", &a);
	func("b", &b);

	return 0;
}

返回值:

a的地址是:009DF98C
b的地址是:烫烫烫烫烫烫烫烫蘛 ?根
a的地址是:009DF98C
b的地址是:009DF983

从上面可以看出,字符型的指针指向变量的地址是乱码,使用void *可以将字符型的内存地址更改为任意合适的类型。

注意:

  • 不能用void申明变量,它不能代表一个真实的变量。
  • 不能对void*指针直接解引用。(需要转换成其他类型的指针)
  • 把其他类型的指针赋值给void*指针不需要转换。
  • 把void*指针赋值给其它类型的指针需要转换。

6 C++的内存模型

内存空间

  • 栈:向下增长(局部变量、函数的参数、返回值)
  • 堆:向上增长(动态开辟内存的变量)
  • 数据段:(全局变量、静态变量)
  • 代码段:(可执行代码、常量区)

栈和堆的主要区别:

  • 管理方式不同:栈是系统自动管理的,在出作用域时,将自动被释放;堆需要手动释放,若程序中不释放,程序结束时由操作系统回收。
  • 空间大小不同:堆内存的大小受限于物理内存空间;而栈就小的可怜,一般只有8M(可以修改系统参数)

7 动态分配内存new和delete

使用堆区的内存有四个步骤:

  1. 申明一个指针;
  2. 用new运算符向系统申请一块内存,让指针指向这块内存;
  3. 通过对指针解引用的方法,像使用变量一样使用这块内存;
  4. 如果这块内存不用了,用delete运算符释放它。

申明内存的语法:new 数据类型(初始值); //C++11支持{}
如果申请成功后,返回一个地址;如果申请失败,返回一个空地址。

释放内存的语法:delete 地址;
释放内存不会失败。

int* ptr = new int(10);
cout << "*ptr=" << *ptr << endl;

*ptr = 20;
cout << "*ptr=" << *ptr << endl;
delete ptr;

返回值:

*ptr=10
*ptr=20

注意事项:

  • 动态分配出来的内存没有变量名,只能通过指向它的指针来操作内存中的数据。
  • 如果动态分配的内存不用了,必须用delete释放它,否则有可能用尽系统的内存。
  • 动态分配的内存生命周期与程序相同,程序退出时,如果没有释放,系统将自动回收。
  • 就算指针的作用域已失效,所指向的内存也不会释放。(一块内存可能有多个指向,不能因为一个指针的作用域失效,其他指针都不能用了)
  • 用指针跟踪已分配的内存时,不能跟丢。

8 二级指针

指针是指针变量的简称,也是变量,是变量就有地址。
指针用于存放普通变量的地址。
二级指针用于存放指针变量的地址。
申明二级指针的语法:数据类型 指针名*
使用指针有两个目的:

  1. 传递地址
  2. 存放动态分配的内存的地址
    在函数中,如果传递普通变量的地址,形参用指针;传递指针的地址的地址,形参用二级指针。
#include
using namespace std;

int main()
{
	int ii = 8;
	cout << "ii=" << ii << "\tii的地址是:" << &ii << endl;
	int* pii = &ii;
	cout << "pii=" << pii << "\tpii的地址是:" << &pii << "\t*pii=" << *pii << endl;
	int** ppii = &pii;
	cout << "ppii=" << ppii << "\tppii的地址是:" << &ppii << "\t*ppii=" << *ppii << endl;

	cout << "**ppii=" << **ppii << endl;

	return 0;
}

返回值:

ii=8    ii的地址是:007DFDA8
pii=007DFDA8    pii的地址是:007DFD9C   *pii=8
ppii=007DFD9C   ppii的地址是:007DFD90  *ppii=007DFDA8
**ppii=8
  1. 把普通的变量的地址传入函数后,可以在函数中修改变量的值;把指针的地址传入函数后,可以在函数中修改指针的值。
#include
using namespace std;

void func(int** pp)
{
	*pp = new int(3);
	cout << "pp=" << pp << ",*pp=" << *pp << endl;
}

int main()
{
	int* p = 0;
	func(&p);
	cout << "p=" << p << ",*p=" << *p << endl;

	return 0;
}

返回值:

pp=008FFC80,*pp=00CF8DC0
p=00CF8DC0,*p=3

对于上述代码,pp存放的是p的地址,即*pp等于p的地址。pp的地址是自己申请的,p的地址是系统自动分配的,在函数func中定义*pp的值等于3,也就是pp指向的内存的值是3。

9 空指针

在C/C++中,用0或NULL都可以表示空指针。
申明指针之后,在赋值之前,让它指向空,表示没有指向任何地址。

  1. 使用空指针的后果
    如果对空指针解引用,程序会崩溃。
    如果对空指针使用delete运算符,系统将忽略该操作,不会出现异常。所以,内存被释放后,也应该把该指针指向空。

在函数中,应该有判断形参是否为空指针的代码,目的是保证程序的健壮性。

  1. 在C++11中使用nullptr
    注意:在linux平台下,如果使用nullptr,编译需要加-std=c++11参数

10 野指针

野指针就是指针指向的不是一个有效(合法)的地址。
在程序中,如果访问野指针,可能会造成程序的崩溃。
出现野指针的情况主要有三种:

  1. 指针在定义的时候,如果没有初始化,它的值是不确定的(乱指一气)
  2. 如果用指针指向了动态分配的内存,内存被释放后,指针不会置空,但是,指向的地址已失效。
  3. 指针指向的变量已超越变量作用域(变量的内存空间已被系统回收)

规避方法:

  1. 指针在定义的时候,如果没有地方指,就初始化为nullptr。
  2. 动态分配的内存被释放后,将其置为nullptr。
  3. 函数不要返回局部变量的地址。
    注意事项:野指针的危害比空指针要大得多,在程序中,如果访问野指针,可能造成程序的崩溃,是可能,不一定,程序的表现是不稳定,增加了调试程序的难度。

11 一维数组和指针

11.1 指针的算术

将一个整型变量加1后,其值将增加1。
但是,将指针变量(地址的值)加1后,增加的量等于它指向的数据类型的字节数。

#include
using namespace std;

int main()
{
	char a;   cout << "sizeof(char)=" << sizeof(char) << endl;
	short b;  cout << "sizeof(short)=" << sizeof(short) << endl;
	int c;    cout << "sizeof(int)=" << sizeof(int) << endl;
	double d; cout << "sizeof(double)=" << sizeof(double) << endl;

	cout << "a的地址是:" << (void*)&a << endl;
	cout << "a的地址+1是:" << (void*)(&a+1) << endl;
	cout << "b的地址是:" << (void*)&b << endl;
	cout << "b的地址+1是:" << (void*)(&b + 1) << endl;
	cout << "c的地址是:" << (void*)&c << endl;
	cout << "c的地址+1是:" << (void*)(&c + 1) << endl;
	cout << "d的地址是:" << (void*)&d << endl;
	cout << "d的地址+1是:" << (void*)(&d + 1) << endl;

	return 0;
}

返回值:

sizeof(char)=1
sizeof(short)=2
sizeof(int)=4
sizeof(double)=8
a的地址是:0035F81B
a的地址+1是:0035F81C
b的地址是:0035F80C
b的地址+1是:0035F80E
c的地址是:0035F800
c的地址+1是:0035F804
d的地址是:0035F7F0
d的地址+1是:0035F7F8

11.2 数组的地址

  1. 数组在内存中占用的空间是连续的。
  2. C++将数组名解释为数组第0个元素的地址。
  3. 数组第0个元素的地址和数组首地址的取值是相同的。
  4. 数组第n个元素的地址是:数组首地址+n
  5. C++编译器把数组名[下标]解释为*(数组首地址+下标)
#include
using namespace std;

int main()
{
	int a[5] = {3,6,5,8,9};
	cout << "a的值是:" << (long long)a << endl;
	cout << "&a的值是:" << (long long)&a << endl;

	cout << "a[0]的地址是:" << (long long)&a[0] << endl;
	cout << "a[1]的地址是:" << (long long)&a[1] << endl;
	cout << "a[2]的地址是:" << (long long)&a[2] << endl;
	cout << "a[3]的地址是:" << (long long)&a[3] << endl;
	cout << "a[4]的地址是:" << (long long)&a[4] << endl;

	int* p = a;
	cout << "p的值是:" << (long long)p << endl;
	cout << "p+0的值是:" << (long long)(p+0) << endl;
	cout << "p+1的值是:" << (long long)(p+1) << endl;
	cout << "p+2的值是:" << (long long)(p+2) << endl;
	cout << "p+3的值是:" << (long long)(p+3) << endl;
	cout << "p+4的值是:" << (long long)(p+4) << endl;

	return 0;
}

返回值:

a的值是:8257184
&a的值是:8257184
a[0]的地址是:8257184
a[1]的地址是:8257188
a[2]的地址是:8257192
a[3]的地址是:8257196
a[4]的地址是:8257200
p的值是:8257184
p+0的值是:8257184
p+1的值是:8257188
p+2的值是:8257192
p+3的值是:8257196
p+4的值是:8257200

11.3 数组的本质

数组是占用连续空间的一块内存,数组名被解释为数组第0个元素的地址。C++操作这块内存有两种方法:数组解释法和指针表示法,它们是等价的。

11.4 数组名不一定会被解释为地址

在多数情况下,C++将数组名解释为数组的第0个元素的地址,但是,将sizeof运算符用于数组名时,将返回整个数组占用内存空间的字节数。

可以修改指针的值,但数组名是常量,不可修改。

12 一维数组用于函数的参数

12.1 指针的数组表示

在C++内部,用指针来处理数组。
C++编译器把数组名[下标]解释为*(数组首地址+下标)
C++编译器把地址[下标]解释为*(地址+下标)

12.2 一维数组用于函数的参数

一维数组用于函数的参数时,只能传数组的地址,并且必须把数组长度也传进去,除非数组中有最后一个元素的标志。
书写方法有两种:

void func(int* arr,int len)
void func(int arr[],int len)

注意事项:
在函数中,可以用数组表示法,也可以用指针表示法。
在函数中,不要对指针名用sizeof运算符,它不是数组名。

#include
using namespace std;

void func(int* arr, int len)
{
	cout << "arr的长度是:" << sizeof(arr) << endl;
	for (int i = 0; i < len; i++)
	{
		cout << "arr[" << i << "]的值是:" << arr[i] << endl;
	}
}

int main()
{
	int a[] = { 1,2,3,4,5,6,7,8 };
	cout << "a[]的大小是:" << sizeof(a) << endl;  //大小用字节表示,一个int型数是4字节
	func(a, sizeof(a)/sizeof(int));

	return 0;
}

13 用new动态创建一维数组

普通数组在栈上分配内存,栈很小;如果需要存放更多的元素,必须在堆上分配内存。
动态创建一维数组的语法:数据类型 *指针=new 数据类型[数组长度];

释放一维数组的语法:delete[] 指针

#include
using namespace std;

int main()
{
	int* arr = new int[8]; //创建8个元素的整型数组
	for (int i=0; i < 8; i++)
	{
		arr[i] = 100 + i;                                         //数组表示法
		cout << "arr[" << i << "]的值是:" << *(arr + i) << endl;  //指针表示法
	}
	delete[] arr;

	return 0;
}

注意事项:

  • 动态创建的数组没有数组名,不能用sizeof运算符。
  • 可以用数组表示法和指针表示法两种方式使用动态创建的数组。
  • 必须使用delete[]来释放动态数组的内存,不能只用delete。
  • 不要用delete[]来释放不是new[]分配的内存。
  • 不要用delete[]释放同一个内存块两次(否则等同于操作野指针)
  • 对空指针用delete[]是安全的,释放内存后,应该把指针置空nullptr
  • 申明普通数组的时候,数组长度可以是变量,相当于在栈上动态创建数组,并且不需要释放。
  • 如果内存不足,调用new会产生异常,导致程序中止;如果在new关键字后面加(std::nothrow)选项,则返回nullptr,不会产生异常。
  • 为什么用delete[]释放数组的时候,不需要指定数组的大小,因为系统会自动跟踪已分配的内存。
#include
using namespace std;

int main()
{
	int* arr = new (std::nothrow) int[100000001]; //创建8个元素的整型数组
	if (arr == nullptr)
		cout << "内存分配失败!" << endl;
	else
	{
		for (int i = 0; i < 8; i++)
		{
			arr[i] = 100 + i;
			cout << "arr[" << i << "]的值是:" << *(arr + i) << endl;
		}
		delete[] arr;
	}

	return 0;
}

14 二维数组用于函数的参数

int* p;            //整型指针
int* p[3];         //一维整型指针数组,元素是3个整型指针(p[0]、p[1]、p[2])
int* p();          //函数p的返回值类型是整型的地址
int (*p)(int,int)  //p是函数指针,函数的返回值是整型

14.1 行指针(数组指针)

申明行指针的语法:数据类型 (*行指针名)[行的大小] //行的大小即数组的长度

int (*p1)[3]   //p1是行指针,用于指向数组长度为3的int型数组
int (*p2)[5]   //p2是行指针,用于指向数组长度为5的int型数组
double (*p3)[5]//p3是行指针,用于指向数组长度为5的double型数组

一维数组名被解释为数组第0个元素的地址。
对一维数组名取地址得到的是数组的地址,是行地址。

#include
using namespace std;

int main()
{
	int a[10];
	cout << "数组a的第0个元素的地址是:" << (long long)(a) << endl;
	cout << "数组a的地址是:" << (long long)(&a) << endl;

	cout << "数组a的第0个元素的地址+1是:" << (long long)(a + 1) << endl;
	cout << "数组a的地址+1是:" << (long long)(&a+1) << endl;

	return 0;
}

返回值:

数组a的第0个元素的地址是:20381120
数组a的地址是:20381120
数组a的第0个元素的地址+1是:20381124
数组a的地址+1是:20381160

int型是4个字节,也就是数组a的大小是4*10=40字节。可以看出数组a的首地址就是第0个元素的地址,第0个元素地址+1,也就是增加4个字节。对于数组a取地址,就是行地址。

14.2 二维数组名是行地址

int bh[2][3] = {{11,12,13},{21,22,23}};

bh是二维数组名,该数组有2个元素,每一个元素本身又是一个数组长度为3的整型数组
bh被解释为数组长度为3的整型数组类型的行地址。

如果存放bh的值,要用数组长度为3的整型数组类型的行指针。

int (\*p)[3]=bh;

14.3 把二维数组传递给函数

如果要把bh传给函数,函数的申明如下:

void func(int (*p)[3],int len);

void func(int p[][3],int len);
#include
using namespace std;

void func(int(*p)[3], int len)
{
	for (int i = 0; i < len; i++)
	{
		for (int j = 0; j < 3; j++)
			cout << "p[" << i << "][" << j << "]=" << p[i][j] << " ";
		cout << endl;
	}
}

int main()
{
	int bh[2][3] = { {11,12,13},{21,22,23} };
	func(bh,2);

	return 0;
}

返回值:

p[0][0]=11 p[0][1]=12 p[0][2]=13
p[1][0]=21 p[1][1]=22 p[1][2]=23

15 多维数组

#include
using namespace std;

void func(int(*p)[2][3])
{
	int num = 1;
	for (int i = 0; i < 4; i++)
		for (int j = 0; j < 2; j++)
			for (int k = 0; k < 3; k++)
				p[i][j][k] = num++;
}

int main()
{
	int bh[4][2][3];

	memset(bh, 0, sizeof(bh)); //初始化数组
	func(bh);

	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 2; j++)
		{
			for (int k=0; k < 3; k++)
			{
				cout << bh[i][j][k] << "\t" ;
			}
			cout << endl;
		}
		cout << endl;
	}
			
	return 0;
}

返回值:

1       2       3
4       5       6

7       8       9
10      11      12

13      14      15
16      17      18

19      20      21
22      23      24

函数的参数还是将三维数组的数组名作为实参,也就是数组的取地址。

16 函数指针和回调函数

16.1 函数指针

函数的二进制代码存放在内存的四区中的代码段,函数的地址是它在内存中的起始地址。如果把函数的地址作为参数传递给函数,就可以在函数中灵活的调用其他的函数。

使用函数指针的三个步骤:

  1. 申明函数指针;
  2. 让函数指针指向函数的地址;
  3. 通过函数指针调用函数。

16.2 申明函数指针

申明普通指针时,必须提供指针的类型。同样,申明函数指针时,也必须提供函数类型,函数类型是指返回值和参数列表(函数名和形参名不是)。

假设函数的原型是:

int func1(int bh,string str);
int func2(int no,string message);
int func3(int id,string info);

bool func4(int id,string info);

bool func5(int id);

则函数指针的申明是:

int (*pfa)(int,string);

bool (*pfb)(int,string);

bool (*pfc)(int);
#include
using namespace std;

void func(int no, string message)
{
	cout << "我是第" << no << "号:" << message << endl;
}

int main()
{
	int no = 3;
	string message = "我是一只傻傻鸟";
	func(no, message);

	// 1. 申明函数指针
	void (*pfunc)(int, string);
	// 2. 函数指针指向函数的地址(函数指针名=函数名)
	pfunc = func;
	// 3. 函数指针调用函数(C++)
	pfunc(no, message);
	// C语言
	(*pfunc)(no, message);

	return 0;
}

17 指针函数

返回值为地址的函数就是指针函数。
[[指针函数和函数指针的区别]]

18 指针和引用的区别

  1. 指针是本身存储的所指向的对象的地址,而引用只是引用对象的别名。
  2. 指针在初始化可为空,指针指向的对象可以改变,而引用则不能为空,必须和引用对象绑定,而且之后不能改变。

18.1 传值 VS 传指针 VS 传引用的区别

举例分析1:

#include
using namespace std;

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

int main()
{
	int a = 10;
	int b = 20;
	swap(a, b);
	cout << "a = " << a << ", b = " << b << endl;

	return 0;
}

返回值:

a = 10, b = 20

如果是直接传递参数,则不会改变原来a和b的值。swap函数中的a和b相当于是局部变量。

举例分析2:

#include
using namespace std;

void swap(int* x, int* y)
{
	int temp;
	temp = *x;
	*x = *y;
	*y = temp;
}

int main()
{
	int a = 10;
	int b = 20;
	swap(&a, &b);
	cout << "a = " << a << ", b = " << b << endl;

	return 0;
}

返回值:

a = 20, b = 10

如果指针作为函数的形参,则直接操作内存地址存储的数据,所以会改变主函数中a和b的值。

举例分析3:

#include
using namespace std;

void swap(int &x, int &y)
{
	int temp;
	temp = x;
	x = y;
	y = temp;
}

int main()
{
	int a = 10;
	int b = 20;
	swap(a, b);
	cout << "a = " << a << ", b = " << b << endl;

	return 0;
}

返回值:

a = 20, b = 10

如果引用作为函数的形参,也会改变主函数中a和b的值。

18.2 指针和引用的区别

#include
using namespace std;

int main()
{
	int a = 10;
	int b = 20;
	int& i = a;
	int& j = b;
	int* ptra = &a;
	int* ptrb = &b;

	cout << "a = " << a << ", b = " << b << endl;
	cout << "i = " << i << ", j = " << j << endl;
	cout << "ptra = " << *ptra << ", ptrb = " << *ptrb << endl;

	cout << "&a = " << &a << ", &b = " << &b << endl;
	cout << "&i = " << &i << ", &j = " << &j << endl;
	cout << "ptra = " << ptra << ", ptrb = " << ptrb << endl;
	
	cout << "sizeof(ptra)=" << sizeof(ptra) << endl;
	
	return 0;
}

返回值:

a = 10, b = 20
i = 10, j = 20
ptra = 10, ptrb = 20
&a = 00AFFB10, &b = 00AFFB04
&i = 00AFFB10, &j = 00AFFB04
ptra = 00AFFB10, ptrb = 00AFFB04
sizeof(ptra)=8

可以看出,i和j就是a和b的别名,&i,&j和&a,&b效果是一样的。而指针ptra,ptrb存放的是a和b的地址。
如果是64位系统,指针的大小是8个字节。如果是32位系统,指针的大小是4个字节。

19 结构体指针

你可能感兴趣的:(C/C++学习,c++,算法,开发语言)