专业编程基础技术教程

网站首页 > 基础教程 正文

C++编程一些常见问题(三)

ccvgpt 2024-08-11 15:01:24 基础教程 9 ℃

21 new 与delete new[]/delete [ ]

(使用new与delete 与malloc不同之处在于 new分配内存 在析构函数中delete)

22 区分new 的三种形态

new operator

C++编程一些常见问题(三)

operator new

placement new

平常使用时 new operator

当时调用过程中 隐藏了 operator 和 placement的调用

重中之重的类

23 明确class和struct的区别

初始化问题

在没有定义构造函数之前,可以用大括号初始化

定义了以后就不能大括号初始化了

class只有成员名全部是public的情况下,才可以用大括号初始化

类的初始化问题

C c { 0,0};

数组的初始化

int *a = new int[3] {1,2,3};

成员变量初始化

class X

{

int a[4];

public:

x():a{1,2,3,4}

{

}

};

//vector的初始化

vector<string> Vs = {"first","second","third"};

map singers =

{{"Lady Gaga", "1+12345"},{"Lady Gaga", "1+12345"}};

关于默认访问权限

class中的默认访问权限是private的

而struct中的访问权限是public

关于默认继承方式

class中的继承方式是private继承

而在struct中的继承方式是public继承

24 C++悄悄做的事

所有的类都会有一个外号

“big three”

一个或者多个构造函数 +一个析构函数 +一个拷贝构造函数

面向对象的三座大山之一

即使没有声明对象,也会存在编译器会自动生成三个函数,成为默认构造函数,默认析构函数,默认拷贝构造函数

C++11中,标准引入了两个指示符delete和default,以便让程序员自行决定需要编译为我们偷偷生成这些函数,delete告诉编译器不产生这个函数,default告诉编译器产生这个函数

CEmpty中包含一个字节 分配 一个字节

25 首选初始化列表实现类成员的初始化

构造函数实现对类的初始化

尽量使用初始化列表

Cstudent::Cstudent(string name,int age,int id):

m_name(name)

,m_age(age)

,m_ID(id)

{

}

例如 :

对const 对象初始化 只能用列表

如果B类中有A类的成员变量只能够用列表初始化

26 明智的拒绝对象的复制操作

例如假设有一个高校的学生财务管理系统,因为每个学生都是一个独一无二的个体,绝对不会存在所有信息一模一样的情况,因此就不能存在一个学生的信息复制给另外一个学生的情况

怎么做呢

只声明不定义是一个好方法,即将类的相应的拷贝构造函数、赋值操作声明为private

并且不给出实现

27(*) 小心自定义构造函数

在声明类体系的时候 编译器会偷偷的声明 bigThree 其中拷贝构造函数和赋值函数都是用于对象拷贝的

所以统称为拷贝函数

编译器在生成拷贝函数会对所有的类一视同仁,不会特殊处理,他只是简简单单的降原对象的每个Non-static数据拷贝到对象中,即所谓浅拷贝

如果类中有动态内存分配,对象中包含资源,问题就会产生,此时需要用到深拷贝

class Cstring

{

public:

Cstring();

Cstring(const char *data);

private:

char *m_pdata;

int m_nsize;

};

Cstring strSrc("Hello everyone!");

Cstring strDst(strSrc);

会出现两个指针指向一个内存的问题,就会存在连续释放两次的危险

为了解决这种个问题,需要使用深拷贝

Cstring::Cstring(const Cstring & rhs)

{

m_pData = new char[strlen(rhs.m_pData)+1];

strcpy(m_pData,rhs.m_pData);

}

Cstring& Cstring::operator=(const Cstring &rhs)

{

if(this==&rhs)

return *this;

if(m_pData!=NULL)

delete m_pData;

m_pData = new char[strlen(rhs.m_pData)+1];

strcpy(m_pData,rhs.m_pData);

return *this;

}

代码例子

#define _CRT_SECURE_NO_WARNINGS

#include<iostream>

#include<cstring>

using namespace std;

class A

{

private:

int a[4];

public:

A() :a{ 1,2,3,4 }

{

for (int i = 0; i < 4; i++)

cout << a[i] << endl;

}

};

class Cstring

{

public:

Cstring(char *data,int a):m_pdata(data)

{

}

Cstring (char *data)

{

m_pdata =data;

}

Cstring(const Cstring & rhs);

Cstring & operator=(const Cstring &rhs);

void play()

{

cout << m_pdata << endl;

}

private:

char *m_pdata;

int m_nsize;

};

Cstring::Cstring(const Cstring & rhs)

{

m_pdata = new char[strlen(rhs.m_pdata) + 1];

strcpy(m_pdata, rhs.m_pdata);

}

Cstring& Cstring::operator=(const Cstring &rhs)

{

if (this == &rhs)

return *this;

if (m_pdata != NULL)

delete m_pdata;

m_pdata = new char[strlen(rhs.m_pdata) + 1];

strcpy(m_pdata, rhs.m_pdata);

return *this;

}

int main()

{

A m;

Cstring strSrc("Hello everyone!");

Cstring strDst(strSrc);

strDst.play();

cout<<"hello world!"<<endl;

return 0;

}

28 拷贝构造函数的深拷贝与浅拷贝的问题

深拷贝是当一个对象需要复制另外一个对象时,这个对象的指针不能直接指向这个对象,而是需要分配内存

标准的构造函数的写法

A::A(const A & m)

{

m_data = new 私有指针对象

strcpy(m_data,m.data);

m_int = m_i;

…….

}

A & A::operator=(const A & m) //重载=运算符

{

if(this==m) return *this;

if(m_data!=NULL)

delete m_data;

m_data = m.data;

strcpy(m_data,m.data);

return *this;

}

补充1 异常处理

try catch机制

如果catch中函数满足条件,就执行try中的函数 ,

补充2

C++提供了关键字explicit,可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生,声明为explicit的构造函数不能在隐式转换中使用。

来自 <https://blog.csdn.net/qq_35524916/article/details/58178072>

谨防构造函数抛出异常而引发问题

当在构造一个对象时,如果存在动态内存分配的情况,如果分配不成功,对象就没有创建成功,这时候是不能调用析构函数的,所以就存在内存泄漏

解决问题的方法就是使用异常处理

下表是对上面层次结构中出现的每个异常的说明:

异常

描述

std::exception

该异常是所有标准 C++ 异常的父类。

std::bad_alloc

该异常可以通过 new 抛出。

std::bad_cast

该异常可以通过 dynamic_cast 抛出。

std::bad_exception

这在处理 C++ 程序中无法预期的异常时非常有用。

std::bad_typeid

该异常可以通过 typeid 抛出。

std::logic_error

理论上可以通过读取代码来检测到的异常。

std::domain_error

当使用了一个无效的数学域时,会抛出该异常。

std::invalid_argument

当使用了无效的参数时,会抛出该异常。

std::length_error

当创建了太长的 std::string 时,会抛出该异常。

std::out_of_range

该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator[]()。

std::runtime_error

理论上不可以通过读取代码来检测到的异常。

std::overflow_error

当发生数学上溢时,会抛出该异常。

std::range_error

当尝试存储超出范围的值时,会抛出该异常。

std::underflow_error

当发生数学下溢时,会抛出该异常。

来自 <http://www.runoob.com/cplusplus/cpp-exceptions-handling.html>

29 多态基类的析构函数应该为虚

当一个派生类对象使用一个基类对象删除,对象没有虚析构函数时,将不会调用析构链

在使用虚析构函数时:

1、类中至少包含一个虚函数

不要去继承没有虚析构函数的类

多态基类的析构函数应该是virtual的,也必须是virtual的,这样才能保证派生类的对象彻底释放

补充:多态存在的条件

1、 要有继承

2、有虚函数的重写

3、父类指针或者引用指向子类只针对或者引用

30 绝对不能让构造函数为虚函数

虚函数的多态机制是使用一个虚函数表

这个表中存放着虚函数的地址

构造函数返回之前,虚函数表还没有建立,因此不允许构造函数为虚

Tags:

最近发表
标签列表