|
3 指针
指向地址的数据类型。
3.1指针简介
- 1.如何获取地址? &---地址运算符
- 2.指针类型?简单数据类型、复合类型、类相对应的指针类型
- 3.指针存储空间?各指针的存储空间都相同,因为他们都指向地址,而一个系统中地址的范围是确定的
- 4.指针赋值?获取变量的地址赋值给对应变量的指针即可
- 5.如何获取指针指向地址存储的值?---*运算符
- 6.注意事项:避免产生野指针
- (1)每个指针变量名,都需要使用一个。
- (2)一定要在对指针应用解除引用(*)运算符之前,将指针初始化为一个确定的、适当的地址。
- (3)不能直接将整数赋值给指针,但是可以使用强制类型转换将整数转换为地址然后赋值给指针。强转的结果也可能引发异常。
3.2 指针与动态内存分配
3.2.1 new
- (1)new创建动态变量:typename * pointer_name = new typename;
- (2)new创建动态数组:typename * pointer_name = new typename[num_elements];
- (3)内存分配失败:如果没有足够的存储空间,则内存分配失败,会引发异常
- (4)存储地点:变量的值存储在栈(stack)内存区域中,new的内存存储在堆(heap)或自由存储区(free store)中
3.2.2 delete
释放内存,由new分配的内存,需要程序员手动释放,如果未释放则会导致内存泄漏(memory leak)。如果内存泄漏严重,则程序将由于不能寻找更多内存而终止。
注意事项:
- (1)不要使用delete释放不是new分配的内存块
- (2)不要使用delete释放同一个内存块两次
- (3)使用new创建的动态变量,对应使用delete(不带方括号)释放内存
- (4)使用new创建的动态数组,对应使用delete []释放内存 要求(3)和(4)一定要一一对应
- (5)对空指针应用delete是安全的
3.3 指针算数
将指针变量加1(or 减1)后,增加(or 减少)的量等于它指向的类型的字节数。
3.4 指针与数组
指针与数组名最大的区别就是,可以修改指针的值,但是不能修改数组名的值。 获取数组元素的值可以使用数组名也可以使用指针
arrayname becomes *(arrayname + i)
pointername becomes * (pointername+i)
sizeof(arrayname)---返回值为整个数组的长度,arrayname不能 += 1
sizeof(pointername)---返回值为指针所占内存空间大小,能+=1
数组名表示第一个元素的地址 对数组名取&地址表示的是整个数组的地址,它是一种特殊的指针:
int (*pas)[20]---一个例子,其他数据类型也可;
sizeof(pas) == 4,如果对pas+1的话,增加的是整个数组的长度
对数组名+1的话,返回值增加了数组中单个元素的长度.
3.5 指针与字符串
在cout和多数C++表达式中,char数组名、char指针以及用括号括起的字符串常量(const char )都被解释为字符串第一个字符的地址。 用括号括起的字符串常量被存储在某一位置,如果将其赋值给指针,则该指针指向该字符串常量,该指针可以获取字符串但是不可以更改字符串 不应该对没有new分配内存的char指针执行cin操作,也不应该尝试获取野指针所指向的内容。 如果给cout提供一个char ,则cout默认打印字符串;如果需要显示改字符串地址,则应该强制类型转换char * 为int 在为char 指针使用new分配内存时,可以根据字符串的长度为其分配内存,但是记得要给'\0'留空间噢。
3.6 指针new动态结构体
创建结构体:见复合类型结构体。 new结构体格式:inflatable * ps = new inflatable;//假设inflatable是我们创建的结构体 使用指针访问new结构体的元素:箭头成员运算符(->),或者(*指针).成员。
3.7 指针数组、数组指针、指针的指针
指针数组:由指针组成的数组 数组指针:指向数组的指针
3.8 举例
代码:
/*
Project name : _9pointer
Last modified Date: 2022年3月14日21点22分
Last Version: V1.0
Descriptions: 指针类型
*/
#include<iostream>
int main()
{
using namespace std;
/*
指针:指向地址的数据类型。
1.如何获取地址? &---地址运算符
2.指针类型?简单数据类型、复合类型、类相对应的指针类型
3.指针存储空间?各指针的存储空间都相同,因为他们都指向地址,而一个系统中地址的范围是确定的
4.指针赋值?获取变量的地址赋值给对应变量的指针即可
5.如何获取指针指向地址存储的值?---*运算符
6.注意事项:避免产生野指针
(1)每个指针变量名,都需要使用一个*。
(2)一定要在对指针应用解除引用(*)运算符之前,将指针初始化为一个确定的、适当的地址。
(3)不能直接将整数赋值给指针,但是可以使用强制类型转换将整数转换为地址然后赋值给指针。强转的结果也可能引发异常。
*/
cout << &#34;指针****************************************************************&#34; << endl;
int* p_int;
char* p_char;
cout << &#34;sizeof(p_int) = &#34; << sizeof(p_int) << endl;//sizeof(p_int) = 4
cout << &#34;sizeof(p_char) = &#34; << sizeof(p_char) << endl;//sizeof(p_char) = 4
int love = 999999;
cout << &#34;love = &#34; << love << endl;//love = 999999
p_int = &love;//指针赋值
cout << &#34;p_int = &#34; << p_int << endl;//p_int = 00EFFC60
cout << &#34;*p_int = &#34; << *p_int << endl;//*p_int = 999999
//注意事项1
int* x, y;//此处声明的是int指针类型的x和int类型的y
//注意事项2
//*x = 1000;//这句执行之后指针会指向一个不知道在哪的地址上,可能会导致一些bug
//注意事项3
//x = 0x99999999;//不允许的
//x = (int*)0x99999999;//允许,但是在使用解除引用*运算符后可能引发异常,因为你也不知道它真正指向了哪里
//cout << &#34;x = &#34; << x << endl;
//cout << &#34;*x = &#34; << *x << endl;
//new
/*
7.new:
(1)new创建动态变量:typename * pointer_name = new typename;
(2)new创建动态数组:typename * pointer_name = new typename[num_elements];
(3)内存分配失败:如果没有足够的存储空间,则内存分配失败,会引发异常
(4)存储地点:变量的值存储在栈(stack)内存区域中,new的内存存储在堆(heap)或自由存储区(free store)中
8.delete:释放内存,由new分配的内存,需要程序员手动释放,如果未释放则会导致内存泄漏(memory leak)。如果内存泄漏严重,则程序将由于不能寻找更多内存而终止。
注意事项:
(1)不要使用delete释放不是new分配的内存块
(2)不要使用delete释放同一个内存块两次
(3)使用new创建的动态变量,对应使用delete(不带方括号)释放内存
(4)使用new创建的动态数组,对应使用delete []释放内存
要求(3)和(4)一定要一一对应
(5)对空指针应用delete是安全的
9.指针算数
将指针变量加1(or 减1)后,增加(or 减少)的量等于它指向的类型的字节数。
*/
cout << &#34;new*******************************************************************&#34; << endl;
int* p_pointer = new int;
int* p_test = p_pointer;
delete p_pointer;//释放分配的内存
//delete p_test;//已经释放的内存块不能再释放,会触发断点。。
p_test = new int[3]{ 1,2,3 };
p_test[1] = 999;//更改动态数组的元素
cout << &#34;p_test[1] = &#34; << p_test[1] << endl;//p_test[1] = 999 //获取动态数组的元素
cout << &#34;p_test = &#34; << p_test << endl;//p_test = 00FF0480
p_test = p_test + 1;
cout << &#34;p_test = &#34; << p_test << endl;//p_test = 00FF0484
cout << &#34;p_test[1] = &#34; << p_test[1] << endl;//p_test[1] = 3
p_test = p_test - 1;
cout << &#34;p_test = &#34; << p_test << endl;//p_test = 00FF0480
cout << &#34;p_test[1] = &#34; << p_test[1] << endl;//p_test[1] = 999
delete[] p_test;//释放动态数组
double* p_double = new double[3]{ 9.9,9.99,9.88 };
cout << &#34;p_double = &#34; << p_double << endl;//p_test = 00FF0484
p_double = p_double + 1;
cout << &#34;p_double = &#34; << p_double << endl;//p_test = 00FF0484
p_double = p_double - 1;
cout << &#34;p_double = &#34; << p_double << endl;//p_test = 00FF0484
delete[] p_double;
/*
10.指针与数组
指针与数组名最大的区别就是,可以修改指针的值,但是不能修改数组名的值。
获取数组元素的值可以使用数组名也可以使用指针
arrayname becomes *(arrayname + i)
pointername becomes * (pointername+i)
sizeof(arrayname)---返回值为整个数组的长度,arrayname不能 += 1
sizeof(pointername)---返回值为指针所占内存空间大小,能+=1
数组名表示第一个元素的地址
对数组名取&地址表示的是整个数组的地址,它是一种特殊的指针:int (*pas)[20]---一个例子,其他数据类型也可;
sizeof(pas) == 4,如果对pas+1的话,增加的是整个数组的长度
对数组名+1的话,返回值增加了数组中单个元素的长度
*/
cout << &#34;指针与数组*************************************************************&#34; << endl;
int arrayname[4] = { 1,2,3,4 };
int* pointername = arrayname;//pointername指向数组第一个元素的地址
cout << &#34;pointername = &#34; << pointername << endl;//pointername = 008FFD74
cout << &#34;*(arrayname + 1) = &#34; << *(arrayname + 1) << endl;//*(arrayname + 1) = 2
pointername = &arrayname[1];//pointername指向数组第二个元素的地址
cout << &#34;pointername = &#34; << pointername << endl;//pointername = 008FFD78
pointername += 1;//allowed
cout << &#34;*(pointername + 1) = &#34; << *(pointername + 1) << endl;//*(arrayname + 1) = 3
//arrayname += 1;//not allowed
int(*pas)[4] = &arrayname;
cout << &#34;(*pas)[2] = &#34; << (*pas)[2] << endl;//(*pas)[2] = 3
cout << &#34;sizeof(arrayname) = &#34; << sizeof(arrayname) << endl;//sizeof(arrayname) = 16
cout << &#34;sizeof(pointername) = &#34; << sizeof(pointername) << endl;//sizeof(pointername) = 4
cout << &#34;sizeof(pas) = &#34; << sizeof(pas) << endl;//sizeof(pas) = 4
cout << &#34;&arrayname = &#34; << &arrayname << endl;//&arrayname = 008FFD74
cout << &#34;&arrayname + 1 = &#34; << &arrayname + 1 << endl;//&arrayname + 1 = 008FFD84
cout << &#34;arrayname = &#34; << arrayname << endl;//arrayname = 008FFD74
cout << &#34;arrayname + 1 = &#34; << arrayname + 1 << endl;//arrayname + 1 = 008FFD78
cout << &#34;pas = &#34; << pas << endl;//pas = 00CFFC94
cout << &#34;pas + 1 = &#34; << pas + 1 << endl;//pas + 1 = 00CFFCA4
/*
11.指针与字符串
在cout和多数C++表达式中,char数组名、char指针以及用括号括起的字符串常量(const char *)都被解释为字符串第一个字符的地址。
用括号括起的字符串常量被存储在某一位置,如果将其赋值给指针,则该指针指向该字符串常量,该指针可以获取字符串到但是不可以更改字符串
不应该对没有new分配内存的char指针执行cin操作,也不应该尝试获取野指针所指向的内容。
如果给cout提供一个char *,则cout默认打印字符串;如果需要显示改字符串地址,则应该强制类型转换char * 为int *
在为char *指针使用new分配内存时,可以根据字符串的长度为其分配内存,但是记得要给&#39;\0&#39;留空间噢。
*/
cout << &#34;指针与字符串*************************************************************&#34; << endl;
char var_char[5] = &#34;Jasm&#34;;
const char* p_varchar = &#34;Booo&#34;;//不允许更改
//根据字符串的长度为其分配内存
char* p_new_var = new char[strlen(&#34;Jasm&#34;) + 1];//strlen()返回的是字符串的长度,不包含&#39;\0&#39;
strcpy_s(p_new_var, 5, &#34;Jasm&#34;);//使用深拷贝,如果是p_new_var = var_char的话是浅拷贝,则new的存储空间就用不上
cout << &#34;p_new_var = &#34; << p_new_var << endl;//p_new_var = Jasm --- 显示字符串
cout << &#34;p_new_var = &#34; << (int*)p_new_var << endl;//p_new_var = 01140280 --- 显示地址
delete[] p_new_var;
/*
12.指针new动态结构体
创建结构体:见_7compound_types
new结构体格式:inflatable * ps = new inflatable;//假设inflatable是我们创建的结构体
使用指针访问new结构体的元素:箭头成员运算符(->),或者(*指针).成员。
*/
cout << &#34;指针new动态结构体*************************************************************&#34; << endl;
struct person {
char name[20];
char id[20];
};
person* Jasmine = new person{ &#34;Jasmine&#34;,&#34;622827131099&#34; };
cout << &#34;Jasmine->name = &#34; << Jasmine->name << endl;//Jasmine->name = Jasmine
cout << &#34;(*Jasmine).id = &#34; << (*Jasmine).id << endl;//(*Jasmine).id = 622827131099
/*
13.指针数组、数组指针、指针的指针
指针数组:由指针组成的数组
数组指针:指向数组的指针
*/
cout << &#34;指针数组、数组指针、指针的指针************************************************&#34; << endl;
person Booo = { &#34;Booo&#34;,&#34;99999999&#34; };
person* p_Booo = &Booo;//这是一个数组指针
person* p_person[3];//这是一个指针数组
p_person[0] = &Booo;//数组指针的第一个元素指向了Booo
p_person[1] = &Booo;//数组指针的第二个元素指向了Booo
cout << &#34;p_person[0]->name = &#34; << p_person[0]->name << endl;//p_person[0]->name = Booo
cout << &#34;(*p_person)->id = &#34; << (*p_person)->id << endl;//(*p_person)->id = 99999999
person ** pp_person = p_person;//这是指向指针数组的指针
person* (*p_p_person)[3] = &p_person;//这是指向 指针数组地址 的指针
cout << &#34;(**p_p_person)->name = &#34; << (**p_p_person)->name << endl;//(**p_p_person)->name = Booo
cout << &#34;(*pp_person)->name = &#34; << (*pp_person)->name << endl;//(*pp_person)->name = Booo
cout << &#34;(*(pp_person+1))->id = &#34; << (*(pp_person + 1))->id << endl;//(*(pp_person+1))->id = 99999999
return 0;
}
运行结果:
指针****************************************************************
sizeof(p_int) = 4
sizeof(p_char) = 4
love = 999999
p_int = 010FFD18
*p_int = 999999
new*******************************************************************
p_test[1] = 999
p_test = 01387280
p_test = 01387284
p_test[1] = 3
p_test = 01387280
p_test[1] = 999
p_double = 01384928
p_double = 01384930
p_double = 01384928
指针与数组*************************************************************
pointername = 010FFCC4
*(arrayname + 1) = 2
pointername = 010FFCC8
*(pointername + 1) = 4
(*pas)[2] = 3
sizeof(arrayname) = 16
sizeof(pointername) = 4
sizeof(pas) = 4
&arrayname = 010FFCC4
&arrayname + 1 = 010FFCD4
arrayname = 010FFCC4
arrayname + 1 = 010FFCC8
pas = 010FFCC4
pas + 1 = 010FFCD4
指针与字符串*************************************************************
p_new_var = Jasm
p_new_var = 01387248
指针new动态结构体*************************************************************
Jasmine->name = Jasmine
(*Jasmine).id = 622827131099
指针数组、数组指针、指针的指针************************************************
p_person[0]->name = Booo
(*p_person)->id = 99999999
(**p_p_person)->name = Booo
(*pp_person)->name = Booo
(*(pp_person+1))->id = 99999999
D:\Prj\_C++Self\_9pointer\Debug\_9pointer.exe (进程 17616)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
3.9 智能指针
由于程序员可能经常会忘记delete掉自己new的空间,由此导致的内存泄漏是很严重的问题,因此提出三种智能指针 auto_ptr, unique_ptr, shared_ptr:auto_ptr是C++98提出的,在C++11中取缔了。 智能指针是定义了模板类,该模板类可以的析构函数可以delete掉所指向的new的内存空间,以此达到精确管理内存的目的。 定义在memory头文件中,在std命名空间中。
3.9.1 智能指针模板类的定义
template<class X> class auto_ptr {
public:
explicit auto_ptr(X* p =0) throw();//throw()意味着不会引发异常
...};
3.9.2 智能指针的使用
auto_ptr<string> ps(new string); // ps an auto_ptr to string
// (use in place of string * ps)
unique_ptr<double> pdu(new double); // pdu an unique_ptr to double
shared_ptr<string> pss(new string); // pss a shared_ptr to string
注意事项
1.由于构造函数中explicit的限制,使用智能指针只能是显示初始化,因此不允许将指针赋值给智能指针 2.智能指针在通常情况下为指针的特点,可以使用*解除指针,也可以使用->访问结构体的元素,等等 3.不允许使用引用显式初始化指针
string vacation(&#34;I wandered lonely as a cloud.&#34;);
shared_ptr<string> pvac(&vacation); // NO!
3.9.3 智能指针分类
多个指针指向同一个内存空间释放内存时会引发异常,因此智能指针有对应的策略: auto_ptr and for unique_ptr:使用了所有权的概念,只允许一个指针拥有一个对象的所有权,当且仅当智能指针拥有该对象的所有权它才可以使用析构函数的delete该对象,他们拥有赋值转让所有权。 unique_ptr更严格,因为他不会轻易失能上一任智能指针,如果程序尝试将一个unique_ptr转让给另一个,则编译器允许在源对象为临时右值时允许,如果源对象具有一定的持续时间,则不允许。 auto_ptr不允许对容器对象使用,unique_ptr允许对容器对象使用。 为什么unique_ptr可以分辨安全或不安全的使用?因为它使用了移动构造函数和右值引用。 shared_ptr:创建一个更智能的指针,用于跟踪有多少智能指针引用特定对象。这称为引用计数。 std::move():将unique_ptr赋值给另一个unique_ptr。 注意事项: 1.auto_ptr和shared_ptr智能指向new分配的内存空间,不能指向new []分配的内存空间; 2.unique_ptr可以使用new和new []分配的内存空间。 3.std::unique_ptr< double[]>pda(new double(5)); // will use delete []
3.9.4 选择一个智能指针
如果需要多个指针指向一个对象,则使用shared_ptr。 如果不需要多个指针指向一个对象,则使用unique_ptr。
3.9.5 unique_ptr和shared_ptr之间的赋值
在源是一个右值时,允许将unique_ptr赋值给shared_ptr;原因是shared_ptr模板中包含了一个将unique_ptr或右值转换为shared_ptr的显式构造函数,shared_ptr接管以前unique_ptr的对象所有权。
3.9.6 举例
代码:
// str1.cpp -- introducing the string class
/*
Project name : _20Smart_pointers
Last modified Date: 2022年4月5日17点33分
Last Version: V1.0
Descriptions: 智能指针
*/
#include <iostream>
#include <string>
#include <memory>
using namespace std;
unique_ptr<string> demo(const char* s);
unique_ptr<int> make_int(int n);
class Report
{
private:
std::string str;
public:
Report(const std::string s) : str(s)
{
std::cout << &#34;Object created!\n&#34;;
}
~Report() { std::cout << &#34;Object deleted!\n&#34;; }
void comment() const { std::cout << str << &#34;\n&#34;; }
};
int main()
{
cout << &#34;auto_ptr, unique_ptr, shared_ptr*****************************************************&#34; << endl;
{
std::auto_ptr<Report> ps(new Report(&#34;using auto_ptr&#34;));
ps->comment(); // use -> to invoke a member function
}
{
std::shared_ptr<Report> ps(new Report(&#34;using shared_ptr&#34;));
ps->comment();
}
{
std::unique_ptr<Report> ps(new Report(&#34;using unique_ptr&#34;));
ps->comment();
}
cout << &#34;多个指针指向统一内存空间,各智能指针的策略**************************************************&#34; << endl;
shared_ptr<string> films[5] =
{
unique_ptr<string>(new string(&#34;Fowl Balls&#34;)),
unique_ptr<string>(new string(&#34;Duck Walks&#34;)),
unique_ptr<string>(new string(&#34;Chicken Runs&#34;)),
unique_ptr<string>(new string(&#34;Turkey Errors&#34;)),
unique_ptr<string>(new string(&#34;Goose Eggs&#34;))
};
//此处使用auto_ptr会引发错误
//auto_ptr<string> pwin;
shared_ptr<string> pwin;
pwin = films[2]; // films[2] loses ownership
cout << &#34;The nominees for best avian baseball film are\n&#34;;
for (int i = 0; i < 5; i++)
cout << *films << endl;
cout << &#34;The winner is &#34; << *pwin << &#34;!\n&#34;;
//对于auto_ptr,#3是允许的,但是程序员可能不小心使用p1,但是p1对于该对象没有权限,会导致错误,这也是取缔auto_ptr的原因
auto_ptr<string> p1(new string(&#34;auto&#34;)); //#1
auto_ptr<string> p2; //#2
p2 = p1; //#3
//cout << *p1;//这句会出错
//对于unique_ptr,就直接不允许#6,所以更安全
unique_ptr<string> p3(new string(&#34;auto&#34;)); //#4
unique_ptr<string> p4; //#5
//p4 = p3; //#6---这句会出错
//但是对于以下这种情况是允许的,因为临时智能指针temp在函数运行结束时会删除对象,不会导致auto_ptr以上出现的问题。
unique_ptr<string> ps;
ps = demo(&#34;Uniquely special&#34;);//allowed
cout << *ps<<endl;//Uniquely special
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string(&#34;Yo!&#34;)); //#2 allowed
//std::move()
unique_ptr<string> ps1, ps2;
ps1 = demo(&#34;Uniquely special&#34;);
ps2 = move(ps1); // enable assignment
ps1 = demo(&#34; and more&#34;);
cout << *ps2 << *ps1 << endl;//Uniquely special and more
unique_ptr<int> pup(make_int(rand() % 1000)); // ok
//shared_ptr<int> spp(pup); // not allowed, pup an lvalue
shared_ptr<int> spr(make_int(rand() % 1000)); // ok
return 0;
}
unique_ptr<string> demo(const char* s)
{
unique_ptr<string> temp(new string(s));
return temp;
}
unique_ptr<int> make_int(int n)
{
return unique_ptr<int>(new int(n));
}
运行结果:
auto_ptr, unique_ptr, shared_ptr*****************************************************
Object created!
using auto_ptr
Object deleted!
Object created!
using shared_ptr
Object deleted!
Object created!
using unique_ptr
Object deleted!
多个指针指向统一内存空间,各智能指针的策略**************************************************
The nominees for best avian baseball film are
Fowl Balls
Duck Walks
Chicken Runs
Turkey Errors
Goose Eggs
The winner is Chicken Runs!
Uniquely special
Uniquely special and more
D:\Prj\_C++Self\_20Smart_pointers\Debug\_20Smart_pointers.exe (进程 5256)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
README
此为本人读C++ Primer总结的笔记,如有错误或知识缺口,请在评论区告知。如本文有在实践中帮到您,是本人的荣幸。 |
|