第四章总结(三)指针初探

    技术2022-07-10  140

    目录

    指针类型delete指针和数组基本操作不解释为首元素地址的特殊情况指针数组和数组指针的简单理解 指针和字符串堆,栈和内存泄漏 二级指针初探

    指针类型

    下面通过代码进行讲解

    #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相当于做了两次 * 号操作,所以可以直接用 . 去访问

    Processed: 0.012, SQL: 9