且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

(五十五)指针

更新时间:2021-11-05 02:37:19

指针跟内存地址有关,他表示的是一个内存地址。

指针操作符为*和&,其中&变量名为变量的内存地址,*变量名为该内存地址的值。

可以通过修改某内存地址的值,来使得其变量的值变化。

如代码:

#include<iostream>

int main()
{
	using namespace std;
	int a=1;	//初始化变量a=1
	int * b;	//初始化变量*b,*b指某地址的值(*是指针操作符)
	b = &a;	//&a为变量a的内存地址,将其地址赋值给变量b(b为内存地址),这个时候*b即为该地址的值(注意,没加*是地址,加了*是值)
	cout << a << endl;	//显示变量a的值
	cout << &a << endl;	//显示变量a的地址
	cout << b << endl;	//显示变量b——是内存地址
	cout << *b << endl;	//显示该内存地址的值
	cout << &b << endl;	//显示储存变量a内存地址(该内存地址是一个值) 的内存地址(是一个地址),相当于该内存地址储存的值,是另外一个值的内存地址
	cout << "————————" << endl;
	*b = 5;	//更改该内存地址的值为5
	cout << a << endl;	//显示变量a的值
	cout << &a << endl;	//显示变量a的地址
	cout << b << endl;	//显示变量b——是内存地址
	cout << *b << endl;	//显示该内存地址的值
	cout << &b << endl;	//显示储存变量a内存地址(该内存地址是一个值) 的内存地址(是一个地址)
	system("pause");
	return 0;
}

输出:


1
0031FE34
0031FE34
1
0031FE28
————————
5
0031FE34
0031FE34
5
0031FE28

注意:

①对于一个变量来说,带&则为其变量的内存地址,并且可以将该内存地址赋值给另外一个值。

②若在内存地址前加*,则为其内存地址的值。即a和*&a的值是相等的。

③可以通过给 表示该 内存地址值 的 变量 赋值,来给 该内存地址的 变量 赋值。即a=1;&a=b;*b=5;cout<<a; 的输出结果是a=5,原因就是a=*b都是同一个内存地址的值(但是表示方式不同),操作该内存地址的值将同时影响两个的值。

④在以上代码中,&a和b都是一个指针,指针表示的是一个地址。

⑤在以上代码中,a和*b是完全等价的,是一体两面,

⑥int * b中,*左右两边是有空格的,但也可以没有空格,或者只有一边有空格,结果是相同的。c程序员这么写int *b,强调*b是一个int类型的值,c++程序员这么写int* b,强调是指向int类型的指针。但两个都是一样的。

 

 

 

 

⑦在以上代码中:

  a ——变量(值);

&a和b ——地址(指针);

  *b ——该内存地址的值;

int a ——int类型的变量(也是值);

int *b ——某内存地址储存了int类型的值(决定了该内存地址的宽度);

(最后两个由于类型都是int,因此占用的内存地址的宽度相同,假如一个是int一个是double,那么在操作的时候就不一样了,特别是值比double大时)

 

⑧int *b; b=&a这两行代码,也可以写为int * b = &a;;但是不能写成int * b = a;也不能写成 int * b;*b = a;原因在于,初始化时,实际上可以理解为b是地址,int * b实际上是int*和b,int*的意思就是指向int类型的内存地址。而不是int 和*b。

int *b=&a;中,int *b整体,被声明为一个指针变量(因此*的存在,b是指针*b是指针变量)。而=&a;这一部分属于初始化部分(initializer),用于初始化前面的变量(b)——推测是右边只影响变量,哪怕变量带运算符也跟运算符无关,因为运算符只是一个变量的修饰。因为b是一个指针,所以需要对指针进行初始化(即地址)。

 

int * a, b;这行代码,创造的是指针a和int变量b,即a为地址,b为一个变量。

int * a, *b;这行代码,则是两个地址,分别是指针a和指针b。

int*a只能被初始化为地址,其中a是地址,int*a,b中,a为地址,b为int变量。

C++的声明,写成T D形式,T是类型名,D是变量名。T可以是各种各样的类型,如整型,浮点类型,数组,字符串,类等,但只是这些类型名,不包含任何符号。

而D是包含符号的各种变量名,符号例如&或者*都是修饰变量名的,例如*a就是指针变量,&b就是内存地址(值b的),像=1;或者=(int*)0x……;也是属于D部分的。

 

总之:int *a中,int*是类型,因为类型,所以a是指针(即地址),所以右边需要赋值地址。

 

(10)在创建指针的时候,***将指针指向一个变量名的地址,而不是将指针所表示地址的值指向另外一个值,例如:

int*a; 

int b=10; 

a=&b;这是ok的,

*a=10;

或者

*a=b;这两个是错误的。

因为指针的关键是地址,运算符*a只不过表示该地址的值,不能先给该地址赋值(如*a=b就是将b的值赋给*a,*a=10就是给该地址赋值为10),因为编译器还不知道指针b到底在哪,因此就算赋值,他也不知道给哪个地址赋值。

而若先a=&b; //给指针a赋值地址

然后*a=5; //给指针a的值赋值5,这样是ok的。

因为编译器知道这个地址是哪里了,于是就可以给这个地址的值赋值。

 

给指针的地址赋值:

在给指针地址赋值时,我们可以通过int *a,b; b=&a;这样,将a的内存地址赋值给指针b。

也可以直接将地址赋值给指针b。例如:

int * a;

a = (int *)0x0037FCB8;

就是直接给指针a赋值地址,需要注意的是,不能直接写地址位置。

例如:

a =0x0037FCB8;

是不可行的,必须加上(int*),表示这个是一个地址,不然编译器会误以为是一个值,而非地址。

另外,地址前面的0x也是不能省略的。

当这样赋值之后,*a显示的就是该内存地址,类型为int的值(注意,必须在之前声明类型即类似int *a,如int或者double,不然编译器不知道显示多长的内存的值)。

另外,内存地址前面的(int *)也不代表指针a的类型是int,指针类型主要靠int *a;这一行来声明。他的主要目的就是表明后面是一个内存地址,而不是一个值。

C++的赋值,要求类型匹配。若只是涉及像整型/浮点类型的转换,只是加了小数点或者省去小数部分,但是若涉及到例如 数组、字符串、结构等,就会产生类型不同,无法赋值。

(int*)是强制类型转换,比如0x0037FCB8是一个值(16进制的),只有加了强制类型转换,于是二者的类型才匹配,否则左边是内存地址,右边是一个值,虽然看起来一样,但本质上二者类型不同,是不能赋值的。

 

 

使用new来分配内存:

在使用变量名的时候,例如int a=1; 系统为变量名a,类型为int,值为1,分配了一个内存用于储存他们。可以通过调用变量名来调用这个值。

 

在使用指针的时候,例如int *b=&a; 则b储存了上面那个变量a所在的地址,而*b可以调用该地址的值。

 

除此之外,我们也可以不使用变量名,直接通过指针调用地址和值,这个时候,关键是使用new这个语句。标准格式为:

类型名 * 指针名=new 类型名;

 

上代码:

#include<iostream>

int main()
{
	using namespace std;
	int *a = new int;	//创建一个指针a,系统为其分配一个类型为int的内存地址(int决定内存宽度)
	*a = 10;	//因为有内存地址了,因此可以给该内存地址赋值10
	cout << *a << endl;	//显示内存地址为a,类型为int的值
	cout << a << endl;	//显示内存地址a
	*a = 100;	//覆写内存地址a的值为100
	cout << *a << endl;
	cout << a << endl;
	cout << endl;
	double *b = new double;	//创建一个指针b,系统为之分配一个类型为double的内存地址(double决定了内存宽度)
	*b = 101.1;	//为该内存地址赋值
	cout << *b << endl;	//显示内存地址b的值
	cout << b << endl;	//显示内存地址b
	cout << *a << endl;	//显示内存地址a的值
	cout << a << endl;	//显示内存地址a
	cout << endl;
	*a = *b;	//将内存地址b的值(类型为double)写到内存地址a中(类型为int),因此小数部分被省略
	cout << *a << endl;	//显示内存地址a的值
	cout << a << endl;	//显示内存地址a
	cout << endl;
	cout << "sizeof(a) = " << sizeof(a) << endl;	//内存地址a的宽度(单位字节)
	cout << "sizeof(*a) = " << sizeof(*a) << endl;	//值*a的宽度
	cout << "sizeof(b) = " << sizeof(b) << endl;	//内存地址b的宽度
	cout << "sizeof(*b) = " << sizeof(*b) << endl;	//值*b的宽度
	system("pause");
	return 0;
}

输出:

10
00730AB0
100
00730AB0

101.1
00731018
100
00730AB0

101
00730AB0

sizeof(a) = 4
sizeof(*a) = 4
sizeof(b) = 4
sizeof(*b) = 8
请按任意键继续. . .

注意:

①在使用new来分配内存地址的时候,具体地址如何,是随机的,如果想分配固定的内存地址,需要使用上面说的int *a=(int*)内存地址; 来进行——虽然个人感觉分配固定地址并不好。

 

②多个new之间,他们的地址都是不同的,不存在后面的new将前面new的内存地址覆写了(因为内存地址在分配的时候,由new后面的类型名决定了内存的宽度,再之后分配的时候,系统会避免在这段内存里分配内存)。

 

③在确定内存地址后,对带运算符的指针(即*a)进行操作,无论是覆写其他值,或者是对该值相加都只会在该内存地址进行。例如*a=10;然后再*a=100,都是在这个内存地址(类型为int)进行写入,不会影响其他内存地址。

 

④可以将其他内存地址的值赋给该内存地址,例如*a=*b; 就是将内存地址b的值101.1赋给内存地址a,但是由于类型不同(b是double,a是int),因此在显示的时候,由a内存地址声明时的类型(int)来决定显示数字——即忽略了*b的小数部分。

 

⑤*a和*b的宽度(sizeof)取决于声明时的类型,如int或者double。int类型的宽度为4,而double类型的宽度是8(在某些系统里会例外)。

也就是说,某个内存地址的值的宽度,和内存地址的宽度是不一样的——因为他们类型不一样,具体看下面:

 

((1))int* 在C++中,是一个类型,这个类型是 指向整型变量的指针。

((2))a是内存地址,也是int* 类型的变量。

((3))sizeof()表示的是某种类型(如int、double,这里是int*)的内存宽度,如果括号里是变量,则显示该变量类型的内存宽度。

((4))而sizeof(a)表示的是 int* 类型的内存宽度,因为a是int*类型。

((5))因为int*类型的内存宽度是4,所以sizeof(a)输出的结果是4

 

 

⑥在使用new的时候,可以在声明的时候使用,也可以在声明后使用。

例如:

int *a=new int;

	int *a;
	a = new int;	//注意,需要使用a(即内存地址),而不是*a这样,类似int *a=内存地址这样

是一样的。这样可以在使用delete释放内存之后(但未删除指针),再次new一个内存地址。


使用delete释放内存:

在使用new请求内存之后,内存地址被占用。同样,也可以通过相反的方式释放内存,如delete。

标准的格式为:

delete 指针名;

注意,指针名前无*  ,表示只是释放该内存地址的值,但指针仍然存在。

上代码:

#include <iostream>

int main()
{
	using namespace std;
	int *a = new int;
	*a = 100;
	cout << "显示指针a的值: " << *a << endl;
	cout << "显示指针a的内存地址: " << a << endl;
	delete a;
	cout << endl;
	//cout << "显示指针a的值: " << *a << endl;	//注意,这行如果加入,会提示编译错误
	cout << "显示指针a的内存地址: " << a << endl;	//注意,这行的地址a和释放内存之前的地址是不同的。
	cout << endl;
	a = new int;	//注意,内存地址已经被释放,因此需要使用new再分配给其一个内存地址
	*a = 50;	//该内存地址的值为50
	cout << "显示指针a的值: " << *a << endl;	//注意,这个时候的值是新内存地址的赋值(之前被赋值50)
	cout << "显示指针a的内存地址: " << a << endl;	//注意,这个时候内存地址已经和之前的内存地址不同了。
	system("pause");
	return 0;
}

输出:


显示指针a的值: 100
显示指针a的内存地址: 00450AB0

显示指针a的内存地址: 00008123

显示指针a的值: 50
显示指针a的内存地址: 00450710
请按任意键继续. . .

注意:

①在释放内存地址之后,如果要继续使用指针,就需要给指针new一个新的内存地址,方法是: 指针名=new 类型名;    ,和之前new一个内存地址的方法相同,只不过不能再次int 指针名=new 类型名 了;

②释放内存地址后,如果要输出(也许还包括调用)该指针的值的话,会出错(因为b没有地址了,既然不存在地址,那么就不可能返回该地址的值;

③释放内存地址的时候,格式是 delete 指针名 即可;delete指针只能删除指针对象,不能删除非指针对象(如值,或加了运算符*的指针);

④目前不太清楚,为什么在释放指针之后,显示指针的内存地址:(显示指针a的内存地址: 00008123)在多次调试之后,结果都是一样的0x0008123,按照解释,地址被释放后,微软为了更容易区分被释放的指针,给被释放的指针一个固定的地址,就是上面的那个。相对而言,指针地址变动是能理解的。

⑤指针释放再new的时候,地址是不同的,由系统自行分配。

⑥不要重复删除一个指针,会出错,例如delete a;delete a;这样,是不对的。

⑦new和delete要配对使用,即用一次new,后面只能用delete进行释放,而不能对一个指针连续new或者delete。

⑧教程说,对空指针使用delete是安全的,空指针指NULL和另外一个,而不是值为0的。

 

 

使用new来创建动态数组:

使用new和使用声明来创建变量,这二者的区别,在于是否在编译阶段占用内存。

例如int a=1; 在编译阶段时,为变量a分配内存空间,宽度为类型int。

而使用new的时候,在编译阶段则不为之分配内存空间,而是在程序运行的时候,根据需要决定是否为之分配内存空间,

假如需要,则分配;假如不需要,则不分配;假如分配了,也可以使用delete指针来释放内存空间。

 

这样的话,new相比声明类型变量,更加灵活,也更加节省内存(因为无需一直占用)。特别是面对大型数据(数组,字符串,结构)

 

使用声明类型变量,在编译时为之分配内存被称为静态联编(static binding),意味着数组(或字符串,结构)是在编译时被加入内存的。

 

使用new,则被称为动态联编(dynamic binding),意味着数组(或其他)是在程序运行时被创建的。这种数组被称为动态数组(dynamic array)。

 

使用静态联编时,需要在编译阶段确定数组的长度,而动态联编,程序在运行时决定数组的长度。

 

创建动态数组的格式为:

类型名 *指针名=new 类型名[数组长度];

例如:创建一个包含5个double类型元素的动态数组办法为:

double *a = new double[5];

类似为指针new一个内存地址。只不过由于后面加了中括号,并且加入数组的元素个数。

同样,释放数组时,也需要使用delete,格式为 delete []指针名;

注意:这个时候,中括号里不加数字,则释放整个数组的内存空间。

 

上代码:

#include <iostream>
int main()
{
	using namespace std;
	double *a = new double[5];	//为数组double分配内存地址
	a[0] = 1.1;
	a[1] = 2.1;
	a[2] = 3.1;
	a[3] = 4.1;
	a[4] = 5.1;
	cout << a[0] << endl;	//这个时候,输出的是数组第一个元素的值,而非地址
	cout << a << endl;	//这里输出的是数组的内存地址
	cout << *a << endl;	//这里输出的是数组第一个元素的值,而非整个数组的
	cout << a[1] << endl;	//输出数组中第二个元素的值
	cout << a[2] << endl;	//由于没有给数组第三个元素赋值,所以输出的并非是我们想要的值
	a = a + 1;	//此时,相当于整个数组括号里的数都减 1。例如原来数组是从a[0]~a[4],-1后是a[-1]~a[3]
	cout << "指针a=a+1,此时数组原来括号里的数字都自动减1.因此:" << endl;
	cout << "a[-1] = " << a[-1] << endl;
	cout << "a[1] = " << a[1] << endl;
	cout << "*a 的值为: " << *a << endl;
	cout << "指针a的地址为: " << a << endl;
	cout << "a[2] = " << a[2] << endl;
	cout << "a[1] + a[2] = " << a[1] + a[2] << endl;
	//delete[0]a;		这种删除方法是错误的,中括号里不能有数字,即不能删除指针a中数组的某一个元素
	a = a - 1;	//在这行命令后,数组从a[-1]~a[3]又变回a[0]~a[4] 。只有这样,才能释放内存
	delete[]a;	//删除包含整个数组的指针a
	cout << a << endl;	//这个时候,地址并非是new时给指针分配的地址了
	system("pause");
	return 0;
}

输出为:

1.1
00560AB0
1.1
2.1
3.1
指针a=a+1,此时数组原来括号里的数字都自动减1.因此:
a[-1] = 1.1
a[1] = 3.1
*a 的值为: 2.1
指针a的地址为: 00560AB8
a[2] = 4.1
a[1] + a[2] = 7.2
00008123
请按任意键继续. . .

我们可以从代码中得出结论:

①在使用动态数组时,可以用指针名代替数组名,即a为指针,a[0]为数组的第一个元素的值,而非第一个元素的地址

②显示指针地址的值*a时,显示的是数组第一个元素的值,而非数组全部元素的值。

③显示指针地址时,显示的是数组第一个元素的地址。

④在给指针+1时,指针的地址右移了(从B0到B8),而数组的位置没变,因此原本指向 a[0]的地址,在右移后(移动了一个double单位),指向了a[1]。这个时候,输出a[0]的命令,实际上输出的是a[1]。于是,想继续输出原本a[0]的话,就必须打a[-1]了。

⑤在移动指针之后,如果想delete指针释放内存,那么就需要将指针移动回来,例如之前是a=a+1; 那么移动回来就是a=a-1; 只有这样,才能正确释放指针。指针加1表示指针右移,减1表示指针左移,并不影响内存地址储存的值。

正因为右移和左移,所以叫指针才名副其实,因为正指的是指针指向的位置。

⑥创建指针时如果加了中括号,那么删除指针时也要加中括号。——有中括号代表数组。 只不过创建动态数组时需声明数组的元素个数,而删除的时候不用,删除只能删除整个动态数组。

⑦a[0]指的是指针所指当前位置内存地址所储存的值,a[1]指的是指针所指内存地址位置再右移(类似指针位置+1)的内存地址所储存的值。也这是为什么数组第一个元素是数组名,然后中括号里的数字为0了。因为0表示当前位置。