c指针和内存之前言

前言

之前一直开发Qt/C++,虽然学过C,但是没在项目中使用过。无论是学习书《C++ Primer》,还是经验书《Effective C++》,都强调要和C分开,当成两种语言学习,导致最近在项目中使用纯C开发很不习惯,也出现了一些问题。

不习惯的地方是没有了类,而最大的问题则是在内存管理上,甚至因为没有了STL,有时需要使用原生数组,而当原生数组和指针牵扯到一块,有时会更加较迷惑。

为了实现泛型, C语言容器使用了大量的void*,C++ STL是通过模板实现的,而且《Effctive STL》中强烈不建议在容器中存放指针,新式智能指针shared_ptr除外。

之前编码一直倾向于Qt style,尤其是管理内存上。尽量不使用裸指针,自动管理内存,除非是第三方库需要,一般代码中极少出现deletefree关键字。

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);

  1. 在c++中指针可以设置默认值,而引用不可以。
  2. 引用无须判断空指针,而指针必须判断。

返回值更多的通过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); //方式1

void Class1::function2(const InType& in, OutType* out); //方式2

各有利弊,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>(); //类似于new和malloc
test->function1();
if(//条件1)
return;

test->function1();
if(//条件2)
return;

if(//条件3)
return;
};

和栈上对象类似,虽然有提前return,也没有delete/free,但此处没有内存泄漏问题。

主要智能指针:

  1. unique_ptr
  2. shared_ptr (引用计数)
  3. 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); //将Test类改为File类
file->open();
if(//条件1)
{
file->close();
return;
}

file->function1();
if(//条件2)
{
file->close();
return;
}

if(//条件3)
{
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(//条件1)
{
return;
}

file->function1();
if(//条件2)
{
return;
}

if(//条件3)
{
return;
}

...
};

容器和Qt隐式共享

STL中式存值的。
QStringQTLQVector,QMap,都是隐式共享的,很多时候直接传值和返回值了。

Qt对象树

Qt中使用对象树(object tree)来组织和管理所有的QObject类及其子类的对象。
当创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。我们创建的这个QObject对象会自动添加到其父对象的children()列表。当父对象被销毁时,这个QObject也会被销毁。

有时候直接继承QObject,让父对象负责管理所有子对象(不止是UI控件), 此时就不用在析构函数中delete了,缺点是浪费资源。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!