前言
之前一直开发Qt/C++
,虽然学过C,但是没在项目中使用过。无论是学习书《C++ Primer》,还是经验书《Effective C++》,都强调要和C分开,当成两种语言学习,导致最近在项目中使用纯C开发很不习惯,也出现了一些问题。
不习惯的地方是没有了类,而最大的问题则是在内存管理上,甚至因为没有了STL,有时需要使用原生数组,而当原生数组和指针牵扯到一块,有时会更加较迷惑。
为了实现泛型, C语言容器使用了大量的void*
,C++ STL
是通过模板实现的,而且《Effctive STL》中强烈不建议在容器中存放指针,新式智能指针shared_ptr
除外。
之前编码一直倾向于Qt style
,尤其是管理内存上。尽量不使用裸指针,自动管理内存,除非是第三方库需要,一般代码中极少出现delete
、free
关键字。
QString
类存在导致几乎没有使用过char*
, 进而不知道还有个全局区(搞C#的时候知道C#有这东西)
C
<——-> C++
<——> Qt
<——> JAVA/C#
传参时,更多的使用引用
c
1 2
| void function1(const InType* in);
|
c++
1 2 3
| void Class1::function1(const InType& in); void Class1::function2(const InType* in = nullptr);
|
- 在c++中指针可以设置默认值,而引用不可以。
- 引用无须判断空指针,而指针必须判断。
返回值更多的通过return
我看的教程中,c语言更推荐通过参数返回,return返回错误码,我想应该是为了提高效率。C++中有编译器返回值优化(将返回值地址分配到调用处的地址上)和右值的使用。另外在桌面软件开发中这么点开销微不足道,更倾向于提高代码的可读性。
1
| int function(const InType* in, OutType* out);
|
《重构》这本书中有一节介绍了过长参数列表
的危害,在实际开发中,见识过这种代码:
1 2
| void Test::function(double parameter1, double parameter2, double parameter3,double parameter4, double parameter5, double parameter6, double parameter7, double& parameter8, double& parameter9,double& parameter10);
|
其中有一个bug就是因为某些参数顺序搞错了引起的,而且所有的输出参数都是用的引用。
C++中,c++之父和Qt都更推荐通过return返回值
比如在Qt中的接口大都类似于如下:
1
| QString &QString::insert(int position, const QString &str);
|
对于如下两种方式:
1 2 3
| void Class1::function1(const InType& in, OutType& out);
void Class1::function2(const InType& in, OutType* out);
|
各有利弊,Qt官方推荐第二种:
1
| int QString::toInt(bool *ok = Q_NULLPTR, int base = 10) const
|
指针和引用做为输出的区别:在调用时,一目了然该参数为指针,调用者可以注意其可能改变输入值。而引用的调用方式和传值完全一样。
C++新标准中甚至加入了std::tuple
,可以一次返回三个值而不用创建一个结构体:
1 2 3 4 5 6
| std::tuple<bool, int , double> Class1::function(const InType& in);
bool value1 = true; int value2 = 0; double value3 = 0; std::tie<bool, int, double>(value1, value2, value3) = std::tuple<bool, int, double>(false, 1, 2.0);
|
在析构函数中delete, 本人很少使用
比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class String{ private: char* p; public: String(int n); ~String(); }; String::~String(){ delete[] p; } String::String(int n){ p = new char[n]; }
void function(){ String s(100); String* ps = new String(200); delete ps; }
|
当String
对象销毁时,会把内部的p
指针一块处理
智能指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void Class1::function() { auto test = std::make_unique<Test>(); test->function1(); if( return;
test->function1(); if( return;
if( return; };
|
和栈上对象类似,虽然有提前return
,也没有delete/free
,但此处没有内存泄漏问题。
主要智能指针:
unique_ptr
shared_ptr
(引用计数)
weak_ptr
(为了解决shared_ptr
循环引用的问题)
自定义delete
对于:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| void Class1::function() { auto file = new File(this); file->open(); if( { file->close(); return; }
file->function1(); if( { file->close(); return; }
if( { file->close(); return; } ... file->close(); };
|
每个return前都要调用file->close()
。
使用智能指针:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| void Class1::function() { std::shared_ptr<File> file(new Test, [](File* _pointer) { _pointer->close(); delete _pointer; });
file->open(); if( { return; }
file->function1(); if( { return; }
if( { return; }
... };
|
容器和Qt隐式共享
STL
中式存值的。
QString
和QTL
如QVector
,QMap
,都是隐式共享的,很多时候直接传值和返回值了。
Qt对象树
Qt中使用对象树(object tree)来组织和管理所有的QObject类及其子类的对象。
当创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。我们创建的这个QObject对象会自动添加到其父对象的children()列表。当父对象被销毁时,这个QObject也会被销毁。
有时候直接继承QObject
,让父对象负责管理所有子对象(不止是UI控件), 此时就不用在析构函数中delete了,缺点是浪费资源。