其实想写这个有一段时间了,可能好多写C++许久的人都不敢说自己精通C++指针,因为这里面水很深。自C++11后智能指针的问世更是把指针的使用升了一个层次。根据自己最近的一些应用,想把智能指针和普通指针作个对比,以供后人学习。这里的比较主要是比较指针之间相互赋值所带来的一些问题。
把一个指针赋值给另一个指针
首先是普通指针,大家来看一下下面的这个程序,复习一下普通指针
int x;
int y;
int *p = &x;
int *q = &y;
x = 35;
y = 46;
p = q; // at this point p is now pointing to the same memory address
// as q, both of them are pointing to the memory allocated to y
*p = 90; // this will also change the values in *q and y
cout << x << " " << y << endl;
cout << *p << " " << *q << endl;
return 0;
猜猜看输出结果是什么呢?输出结果是
35 90
90 90
有一个比较有意思的地方是,*q
的值改变了,本来之前应该是x
即35而现在是90.可以看到我们程序开始把y
的地址赋值给了q
,*q
就应该是46了。之后我们没有再对q
赋值而是把q
赋值给了p
。最后我们改变了*p
的值没想到也改变了*q
的值。原来当程序走过p = q
后,p
和q
都指向相同的地址了,改变p
指向的值就是改变q
指向的值。这个简单的问题竟然在stackoverflow里有了数万的浏览了,见链接
https://stackoverflow.com/questions/7062853/c-pointer-assignment/7062888
使用new初始化普通指针?不要使用new
我想在早期的C++教材或者程序或多或少能看到new的身影,必须要谈及之前的人们为什么要使用它。网上有很多答案,你看了之后觉得懂了,但很可能其实你不懂。最普遍的答案比如new可以让你手动管理内存,使用new初始化了指针之后必须要用delete才能删除指针以及指针指向的内容(大家对边搜为什么要使用new之类的就可以看到类似的答案)。比如下面这个简单的程序
int a = 5;
{
int *p;
int *b = new int;
p = &a;
b = &a;
}
定义了一个普通指针p
以及new定义了一个指针b
。作为一个局部指针,p
在出了大括号{}
后就自动消失了。但是b
不然,b
作为一个new开辟了内存空间的指针,在出了{}
之后,它开辟的内存空间(大小为一个int
所占的字节)是仍然存在的,如果没有delete
,那块内存空间就会一直放在那儿,甚至在程序结束之后都在那儿,一直不能使用了,这就是所谓内存泄漏。
但是!!!!!!!
在出了局部{}
之后,指针b
指向的内容虽然存在,但是指针b已经不可使用了!它的作用域只在{}
内。比如你在括号外使用b
int a = 5;
{
int *p;
int *b = new int;
p = &a;
b = &a;
}
std::cout<<*b<<std::endl;//Wrong. b is not defined
程序会告诉你b不存在,没有定义。b
相当于就是一个局部指针,它只能在局部范围使用,虽然它开辟的内存空间还在,但是它已经不在了(听起来这么别扭)....你想要在括号外delete它也是不行的。
int a = 5;
{
int *p;
int *b = new int;
p = &a;
b = &a;
}
delete b;//Wrong. b is not defined
当前情况下(后面会讲其他情况),你必须在这个局部范围内把它给delete了。
int a = 5;
{
int *p;
int *b = new int;
p = &a;
b = &a;
delete b;//Correct
}
那么问题来了,既然专门开辟了内存空间的指针也只能作为局部的指针使用,那么我TM为什么要用它???在这个局部范围之内,p
指针的作用和它一模一样,出了局部范围,两个指针都不能使用。b
指针还必须在局部范围内手动删除,那么我TM为什么要用它???
百度一下关于new的知识,甚至会有错误答案,比如下面这个链接
https://blog.csdn.net/qq_18884827/article/details/52334303
这是我在搜索c++什么时候使用new
时的第一个链接,但是很遗憾里面的程序是错误的。它大概说使用了new,指针就可以全局使用了,刚才我们已经说了不可以。它举的例子如下
void func()
{
Student *st=new Student;
st->name="hello";
}
void main()
{
func();
cout<<st->name;//原博主说的`正确的调用`,其实错误
}
但是你随便写一个Student类来补充完整程序,在main函数中调用st
程序同样会告诉你st
不存在。
但是下面这种情况下,new定义的指针就可以在函数外使用了,我们把程序做了微调并补充完整
class Student{
public:
std::string name;
};
Student* PrintName(){
Student *st = new Student;
st->name = std::string("ABC");
return st; //return the pointer
}
int main(){
Student* st = PrintName();
std::cout<<st->name<<std::endl;
delete st;
}
运行这个程序你会发现你可以得到输出ABC
。也就是说可以把new定义的指针作为值传递出去。而普通的指针是不行的。
class Student{
public:
std::string name;
};
Student* PrintName(){
Student *st;
st->name = std::string("ABC");
return st; //return the pointer
}
int main(){
Student* st = PrintName();
std::cout<<st->name<<std::endl;
}
运行上面这个程序,会出现段错误。new定义的指针确实可以应用到更广的范围,但需要我们上面的程序那样把它"传递"出去。这TM才是我们为什么要用它,并不是可以直接当全局变量。
第二个要用它的原因,很多文章都说了,new是定义了动态分配的内存。比如下面这个百度的回答
https://zhidao.baidu.com/question/553749984.html
它说而一般直接声明数组时,数组大小必须是常量
。其实不然。下面这个函数
void func(int i){
int p[i];
}
是可行的,你可以随时传递一个i进去。也就是在传递进去一个i
之前这个数组大小不定。下面这个函数
void func(int i){
int* p[i];
//*p[0] = 0;
}
也是可行的。数组指针p
的要指的数组的大小一开始也不确定。传入了i
之后可以再给指针指向的地址赋值。
所以百度上错误答案或者get不到点的答案还挺多的。我也去StackOverflow上之类的查了一下,很多回答比如
https://stackoverflow.com/questions/655065/when-should-i-use-the-new-keyword-in-c
也切不到关键点。
还有一点我们要注意的是,一旦delete了new定义的指针,它指向的东西也会被销毁。下面的代码可以一目了然
int a = 5;
{
int *p;
int *b = new int;
p = &a;
b = &a;
delete b;
}
std::cout<<a<<std::endl;
运行这个程序,你会得到segmentation fault
之类的错误。因为delete b的同时,a也没了,自然不能cout
了。
关于new的第一个要使用的原因,其实也有很多代替品。智能指针出现后更是了。所以我们的总结再次强调,C++11后,没有用智能指针的话
不要使用new, 不要使用new, 不要使用new!!!
智能指针赋值
智能指针,自问世以后大量被使用。目前常用的有shared_ptr
,unique_ptr
以及weak_ptr
。前面两个使用最多,他们的定义方式如下(整型指针举例)
std::unique_ptr<int> ptr0 = std::make_unique<int>(); //C++14 以及之后才可以
std::shared_ptr<int> ptr1 = std::make_shared<int>();//C++11可以
//or
std::unique_ptr<int> ptr0(new int);
std::unique_ptr<int> ptr1(new int);
尽量使用第一种方法,这也是C++官方推荐的。如果要初始化
std::unique_ptr<int> ptr0 = std::make_unique<int>(5); //C++14 以及之后才可以
std::shared_ptr<int> ptr1 = std::make_shared<int>(5);//C++11可以
//or
std::unique_ptr<int> ptr0(new int(5));
std::unique_ptr<int> ptr1(new int(5));
这样指针指向的值就是5。weak_ptr
的构造方法有些不同,它必须从shared_ptr
来创建,有兴趣的同学自己查一下,我也没用过这个指针。
先说一下我使用得最多的shared_ptr
。顾名思义,这个指针是可以共享的。所谓共享,就是如果你有两个指针如下
std::shared_ptr<int> ptr0 = std::make_shared<int>();
std::shared_ptr<int> ptr1 = std::make_shared<int>();
int a = 5;
*ptr0 = a; //ptr0 = &a is wrong!!!
ptr1 = ptr0;//share
最后一行使得ptr1和ptr0共享资源a
。shared_ptr
使用计数机制来表明资源被几个指针共享。当我们调用release()时,当前指针会释放资源所有权,计数减1。当计数等于0时,资源会被释放。注意我的注释,虽然仍然是指针,给地址的方法是无法给shared_ptr
赋值的。大家可能一时半会儿不能体会到这种共享的强大。我给大家举个简单的栗子。
std::shared_ptr<int> ptr0 = std::make_shared<int>();
{
std::shared_ptr<int> ptr1 = std::make_shared<int>();
int a = 5;
*ptr1 = a;
ptr0 = ptr1;
}
std::cout<<*ptr0<<std::endl;
还记得我们之前说的我们简单的使用int* p = new int
的这种初始化指针的方法,如果初始化在局部范围,根本不能在局部外使用p
。而有了shared_ptr
,这个小愿望就可以达成了。上面的程序,我们在局部{}
内定义了智能指针ptr1
。并把它赋值给了ptr0
,此时共享指针计数为2。在局部范围之外,虽然ptr1
消失了,a
也消失了,此时共享指针的计数为1,但我们仍然可以通过*ptr0
获取到之前a
储存的值,也就是5。所以我们cout
最后能得到5。这就好像,a
的肉身(括号之外std::cout<<"a"<<std::endl
是不行的)消失了,但是a
的灵魂还在(你仍然可以获得5)。这才像"全局"的样子!
即使你new了一个全局的指针和一个局部指针,普通的new
能这样吗?不能的。
int* ptr0 = new int;
{
int* ptr1 = new int
int a = 5;
ptr1 = &a;
ptr0 = ptr1;
delete ptr1; //you have to delete ptr1 here or there will be memory leak!
//But then `a` will disappear
}
std::cout<<*ptr0<<std::endl;
delete p1;
正如我注释里所说,你必须在局部把ptr1 delete了。不然就就内存泄露了,但是一旦delete 了ptr1,a
就没了,灵魂和肉身都没了...
可能你又想,这有什么用??我遇到过一些情况,我在局部范围内获得了一些点云。不知道点云的朋友没关系,总是就是很大的数据量,而表示点云的库一般大量使用指针/智能指针去表示数据,因为大的数据最好通过指针获得,防止拷贝消耗内存时间等。要把局部的点云传到局部之外,shared_ptr
再好用不过了。
unique_ptr
其实没什么特别好说的,它作为指针的用处和shared_ptr基本一样,但是顾名思义,是"unique"的。不可拷贝复制。在某些特殊情况下你不希望自己的指针赋值给其他指针,就要用unique_ptr。下面这个程序会编译失败
std::unique_ptr<int> ptr0 = std::make_unique<int>(5) ;
std::unique_ptr<int> ptr1 = std::make_unique<int>() ;
ptr1 = ptr0;//unique_ptr cannot be copied!!!