指针类型
下面通过代码进行讲解
#include<iostream>
using namespace std;
int main()
{
int* pt = new int;
double* ptt = new double;
cout << pt << endl;
cout << ptt << endl;
*pt = 100.00;
*ptt = 12.1234;
cout << *pt << endl;
cout << *ptt << endl;
return 0;
}
这里可以看到,我们通过new申请得到的两块地址的长度是一致的,但是为什么在cout的时候,编译器知道该输出int还是输出double呢?这就与指针的类型有关了
我们在new int的时候,new找到一个长度为四字节的内存块,把起始地址放在pt中,ptt也是同理,,由于地址在计算机中的表示形式都是一样的,最起码在同一台计算机中是这样的,所以pt和ptt存储的内容长度都一致,但是由于pt在定义的时候就定义成了int *类型的变量,所以在cout<<*pt的时候,编译器就知道,从pt存储的那块地址开始的四个字节按照int的方式进行解释
delete
delete用来释放new申请得到的空间,注意 是new 得到的空间,另外new和delete应该成对出现,然后不要对同一个指针,因为第一次delete已经把指针所保存的空间归还给操作系统了,连续两次delete虽然不会报错,但是会返回错误的结束码.
对于数组的申请
int *pt = new int[5]; delete [] pt;
指针和数组
基本操作
直接讲解书中代码,进行引出
#include<iostream>
using namespace std;
int main()
{
double wages[3]{ 1000.0, 2000.0, 3000.0 };
short stacks[3]{ 3,2,1 };
double* pw = wages;
short* ps = &stacks[0];
short(* ps_2)[3] = &stacks;
cout << "pw= " << pw << " *pw=" << *pw << endl;
pw = pw + 1;
cout << "after add 1 to pw\n";
cout << "pw= " << pw << " *pw=" << *pw << endl;
cout << "ps= " << ps << " *ps=" << *ps << endl;
ps = ps + 1;
cout << "after add 1 to ps\n";
cout << "ps= " << ps << " *ps=" << *ps << endl;
cout << "*(stacks+1)=" << *(stacks + 1) << endl;
cout << "sizeof(wages):" << sizeof(wages) << endl;
cout << "sizeof(ps):" << sizeof(ps) << endl;
cout << "&stacks[2]=" << &stacks[2] << endl;
cout << "ps_2+1=" << ps_2 + 1 << endl;
return 0;
}
运行结果
pw= 010FFAB4 *pw=1000
after add 1 to pw
pw= 010FFABC *pw=2000
ps= 010FFAA4 *ps=3
after add 1 to ps
ps= 010FFAA6 *ps=2
*(stacks+1)=2
sizeof(wages):24
sizeof(ps):4
&stacks[2]=010FFAA8
ps_2+1=010FFAAA
对结果进行解释,由于C++一般情况下将数组名结束为数组的第一个元素的地址(特殊情况下面会讲),所以pw的值也就是&wages[0],所以pw也就是wages[0],然后pw=pw+1,对指针变量加1后,其增加的值等于指向的类型占用的字节数,这点很重要!!! 所以这里pw的值和pw都编程了wages[1]的了
指针ps的结果和pw一致,然后对于 *(stacks + 1) ,可以看出这种写法与stacks[1]的结果一致,所以大多数情况下,可以用相同的方式使用指针名和数组名,所以ps[0]这种写法也可以得到stacks[0]的值
最后的两个sizeof的结果,如果是sizeof(wages),得到的结果就是元素的大小*数组的长度,如果是sizeof(指针)的话,得到的结果与编译器或操作系统的位数有关,在vs2019中,如果最上面设置的是x86,则指针大小得到4,如果是x64,则得到8
不解释为首元素地址的特殊情况
- 对数组取地址
同时也是上面这段代码最后一个需要注意的地方,就是short(* ps_2)[3] = &stacks;,我们这里对数组进行了取地址,这时就会得到整个数组的地址,由于整个数组是一个有着3个short元素的数组,虽然ps_2和&stacks[0](也就是stack)的值是一样的,但是从概念上说,&stacks[0]是一个4字节的内存块的地址,而ps_2是一个12字节的内存块的地址,因此最后将ps_2+1得到的地址比&stacks[2]还大了四个字节
指针数组和数组指针的简单理解
所以这里把ps_2声明成了 short (*)[3],这是一个数组指针,这个怎么理解呢,由于括号的优先级,所以ps_2先和星号结合,成为一个指针,然后指向的元素是有着三个元素的short数组(数组指针,首先是个指针,指向一个数组,所以叫数组指针)
然后如果我们去掉括号的话,就成了 short *ps_2[3],ps_2将先和[3]结合,所以就解释为了,ps_2是一个数组,数组的元素是指向short类型的指针,先是一个数组,再是一个指针,所以叫它指针数组
指针和字符串
同样也是讲解书中代码,然后引出相关知识
#include<iostream>
using namespace std;
int main()
{
char animal[20]{ "bear" };
const char* bird = "wren";
char* ps;
cout << animal << " and " << bird << endl;
//cout << ps << endl; 编译错误,使用了未初始化的局部变量ps
//cout << ps << endl; 如果把ps初始化为nullptr,则运行不会报错,但是会返回错误的结束码
cout << "Enter a kind of animal:";
cin >> animal;
ps = animal;
cout << ps << "!\n";
cout << "Before using strcpy():\n";
cout << animal << " at " << (int*)animal << endl;
cout << ps << " at " << (int*)ps << endl;
ps = new char[strlen(animal) + 1];
strncpy_s(ps,strlen(animal) + 1,animal,strlen(animal));
cout << "after use:\n";
cout << animal << " at " << (int*)animal << endl;
cout << ps << " at " << (int*)ps << endl;
delete[] ps;
return 0;
}
bear and wren
Enter a kind of animal:tigger
tigger!
Before using strcpy():
tigger at 00BFFE4C
tigger at 00BFFE4C
after use:
tigger at 00BFFE4C
tigger at 00EC4968
- 为什么"wren"一定要通过const char *去存储?
因为"wren"实际表示的是字符串的地址,因此将地址只能赋给指针,而且由于字符串字面值是常量,所以要是const,表示 bird 是一个指针,指向一个const char的数据,不能对指针指向的数据进行修改 - 一般来说,提供给cout一个指针,他将打印地址,但如果指针类型为char *,则cout将显示指向的字符串,但如果要显示地址得话,就需要想上面一样转换一下再输出
- 如果要进行字符串副本的话,不能直接用 = 因为这样实际上是让两个指针指向了一个地址,就像上面 ps =animal之后的结果,两个的地址一样, 这样并不安全,通常的做法是 先给ps开辟空间,然后用strcpy函数进行拷贝
当然,如果使用string的话,就避免了这些问题,这些就不用我们考虑了,之前有介绍string的自动扩容 - C++不保证字符串字面值被唯一的存储,也就是说如果在程序中多次使用了 “bear” 这个字符串常量,则编译器可能存储该字符串的多个副本,也可能只存储一个副本,这种与编译器有关的我们就不做深究了,我在vs2019和DEV C++上测试,都是只存储了一个
堆,栈和内存泄漏
#include<iostream>
using namespace std;
int main()
{
{
int* a = new int;
}
delete a; //直接报错,因为a的作用域已经结束了
return 0;
}
书上说的就是这么两汉代码,我们在{}中 new 了一个空间给a,但是花括号结束也就代表着出了a的作用域,在括号外就无法通过a去delete了
二级指针初探
#include<iostream>
using namespace std;
struct mytest {
int test;
};
int main()
{
mytest t1, t2, t3;
t1.test = 1996;
t2.test = 1997;
t3.test = 1998;
mytest * arp[3] = { &t1, &t2, &t3 };
mytest* *pd = arp;
cout << pd[0]->test << endl;//1996
cout << (*(pd + 0))->test << endl;//1996
cout << (*pd)[0].test << endl;//1996
cout << pd[0]->test << endl;//1996
cout << arp[0]->test << "--" << (*(arp + 0))->test << "--" << (*(*(arp + 0))).test << endl;//1996--1996--1996
}
由于arp是一个指针数组,所以他的每一个元素都是指针,所以arp[0]就需要用间接成员运算符也就是箭头去访问成员,由于前面介绍过 arp[0] 和 (arp+0)是一回事,但是最后那个((*(arp + 0)))就直接拿到的是这个结构体,所以可以使用 . 去访问元素
然后arp由于是一个数组的名称,因此它是第一个元素的地址,但其第一个元素为指针,所以pd就是一个指针变量,它指向一个mytest 的指针,所以*pd就是结构体指针,需要用箭头去访问,然后(*pd)[0].test相当于做了两次 * 号操作,所以可以直接用 . 去访问