【C++学习(10)】将一个类作为另一个类的成员: 类对象和类指针; std::unique_ptr 智能管理类指针

文章目录

  • 类对象
  • 类指针
  • c++如何在一个类中定义指向另外一个类的指针
    • 一, 在一个类中定义另外一个类的成员为指针类型。并用this指针管理对象的内存,实现数据的传递。
    • 二、通过对象进行内存分配,从而进行管理new一个对象。
  • 5 std::unique_ptr 智能管理类指针
    • 一、unique_ptr初始化
    • 二、无法进行复制构造和赋值操作
    • 三、可以进行移动构造和移动赋值操作
    • 四、可以通过函数返回值返回unique_ptr
  • 参考

类对象

  1. 首先,要将A类的对象作为B类的成员,你必须在B类声明前声明A类。这样,在B类声明中,就知道A类是一个类,不然编译器是不知道A这个字母代表什么,只有在B类声明前声明了A类,
    B类中就知道A代表的是一个类,是一种自定义类型。要达到这个效果,需要在B类声明前,包含A类的头文件,A类的头文件中就是A类的声明,有A类的数据成员,也有成员函数等。
  2. 其次,就是像声明一个成员变量一样,在B类中添加一个成员,如A m_a; 这句表示使用A类声明了一个B类的成员变量m_a。但是在此,只是对m_a成员变量声明而已,并没有分配内存空间,
    具体的声明定义和初始化等,请阅读《C/C++声明定义初始化和赋值独家剖析深刻理解》。
  3. 最后,初始化m_a。因为这个成员是一个类对象,因此需要调用构造函数才能初始化。因为这个对象m_a在B类中,m_a对象的构造必然是在B类对象构造之前进行,因此不能在B类构造函数中进行,
    又因为m_a不是全局对象,也不能在函数外部。如何解决这个问题呢?这就是C++提出的成员初始化列表,用来解决这类问题的。如果A类提供了默认构造函数,不需要参数的,那就不需要显式初始化,
    让编译器自动完成即可,但是有时候需要在构造时就需要传值,此时就只能通过成员初始化列表。
/*-------------------------------------初始化例子------------------------------------------*/
class Data
{
public:
    Data(int data): m_iData(data){ cout << "Data::Data()" << endl; }
    ~Data(){ cout << "Data::~Data()" << endl; }
    void setData(int data) { cout << "Data::setData" << endl; m_iData = data; }
    int getData() { return m_iData; }
private:
    int m_iData;
};

class TData
{
public:
    TData(): m_cData(5){ cout << "TData::TData()" << endl; }
    ~TData(){ cout << "TData::~TData()" << endl; }
    void printData() { cout << "data = " << m_cData.getData() << endl; }
private:
    Data m_cData;
};


int main()
{
    TData d1;
    d1.printData();
    return 0;
}
结果:
Data::Data()
TData::TData()
data = 5
TData::~TData()
Data::~Data()

注意:
如果Data类的构造函数是无参的,那么在TData类的构造函数初始化列表中无需初始化(不需要显式初始化)。

类指针

初始化
类指针是指向类的指针变量。在C++中,类指针可以用于动态创建对象、访问类的成员和调用类的成员函数。

类指针的声明方式与普通指针相同,只需在类型前面加上类名和*号,例如:

ClassName* ptr;

然后,可以使用new运算符为类指针动态分配内存并创建对象:

ptr = new ClassName;

接下来,可以使用箭头运算符(->)来访问类的成员变量和成员函数:

    ptr->memberVariable;
    ptr->memberFunction();

当不再需要对象时,应使用delete运算符释放内存:

delete ptr;

这样可以确保在对象不再使用时释放内存,避免内存泄漏。

类指针的使用可以实现动态的对象创建和管理,提供更灵活的编程方式。

class Data
{
public:
    Data(){cout << "Data::Data()" << endl;}
    ~Data(){}
    void setData(int data) { cout << "Data::setData" << endl; m_iData = data; }
    int getData() { return m_iData; }
private:
    int m_iData;
};


Data a; //实例化一个类对象a
a.setData(1);
 
// Data *b = new Data();
Data *b; //实例化一个类指针b
b =  new Data();
b->setData(2);

这种方法比较便捷,省去了初始化的麻烦,且可以动态的创建对象,但是也会带来内存的使用问题。初学者推荐使用第一种方法。指针的使用,需要一定功底,如果使用不当,
会出现内存访问违规或者内存泄露问题。指针的深入理解,请参考《指针的深入理解,深刻掌握指针》。

  1. 首先,在B类声明前,包含A类头文件,这个第一个方法的是一样的解释,即使是声明指针,也要包含,因为要使用A代表一个类,就必须先包含A类头文件告诉编译器A是一个类的代名词而不是简单的字符。
  2. 其次,声明A类指针作为成员,如A * pA;这样就可以声明了。很简单,这个指针和普通指针一样大,并不占用很多内存。在很多要动态创建很多对象时特别方便。用完就释放,需要就创建。
  3. 然后,就是初始化指针。在B类构造函数中,初始化时将这个指针设为NULL。这是规范的写法,之后创建对象后便指向了对象,此时指针就不是NULL了,删除指针指向的对象后,一定要将指针值设置成NULL。
    这样,要判断指针是否指向一个对象,就只要判断指针是否为NULL即可。
  4. 最后,在使用时,需要A类对象时,new一个A类对象,然后赋值给pA,这样pA就指向了new出来的对象,然后都用指针来操作A。用完后,使用delete pA 。

特别说明:为了防止内存泄露,new了对象后,一定要delete这个对象。最容易出现内存泄露的就是频繁的new对象和delete对象,导致前一个指向的对象没有删除就new了一个新对象给指针,
最后之前的对象就无法使用,知道程序结束才能被释放,这就是内存泄露了。正确的代码写法如下:

-----------------代码-------------------------------
if(NULL != pA)//此处需要初始化时设置指针为空。
{
  delete pA;
  pA = new A;
}  else {
  pA = new A;
}
-----------------代码-------------------------------

c++如何在一个类中定义指向另外一个类的指针

一, 在一个类中定义另外一个类的成员为指针类型。并用this指针管理对象的内存,实现数据的传递。

直接定义即可。比如已有一个类class A, 再定义一个类class B,这样在B的定义中,就可以有一个A*类型的成员变量。
比如

class B
{
    A * a;

};

这里的B::a就是一个A*类型的指针。
需要注意的是,要使用这种形式的定义,要么A的定义在B的上方,要么在B定义前加一个A类的声明。

class A;
class B
{
    A * a;

};

二、通过对象进行内存分配,从而进行管理new一个对象。

类名 *指针名
如 在一个类中定义指向另外一个类的指针:

class A {
public:
A();
~A();
.....

B *pt; //这个就是你要的,直接定义就好,但是记得要包含类B 的头文件

}

追问

那如何给改指针分配动态内存?

追答

我是这么做的:

pt = new B;

这个是在A的构造函数里面定义的,别忘记在A的析构函数里面: if (B) delete pt;

追问

thanks!如果b的构造函数有1个int型的参数,那就是pt = new B(5);对不对?那这个动态内存的分配能直接放在类里面吗?我试过不行啊,后来放到函数里面动态分配才行。为什么

追答

直接放到类里面肯定是不行的,因为类里面是不允许初始化的。

5 std::unique_ptr 智能管理类指针

std::unique_ptr 是 C++ 标准库中的智能指针类,用于管理动态分配的对象。它提供了独占式拥有权,即在任何时候只能有一个 std::unique_ptr 拥有对对象的唯一所有权。当 std::unique_ptr 被销毁或重置时,它会自动删除所管理的对象,从而避免内存泄漏。

std::unique_ptr 的主要特点和用法如下:

  • 独占式所有权:一个 std::unique_ptr 实例拥有对对象的唯一所有权,不能拷贝或共享所有权。这意味着只能有一个 std::unique_ptr 实例指向同一个对象,从而避免了资源的多重释放和访问冲突。

  • 托管动态分配的对象:std::unique_ptr 主要用于托管通过 new 或 std::make_unique 动态分配的对象。它可以管理任何可删除的对象,包括基本类型、自定义类型和数组等。

  • 自动释放资源:当 std::unique_ptr 被销毁或重置时,它会自动调用所管理对象的析构函数,并释放对象所占用的内存。这消除了手动管理资源释放的需求,提高了代码的可靠性和安全性。

  • 移动语义:std::unique_ptr 支持移动语义,可以通过移动构造函数和移动赋值运算符将所有权从一个 std::unique_ptr 实例转移给另一个实例,从而避免不必要的对象拷贝和资源释放。

一、unique_ptr初始化


#include 
#include 

class UniquePtr {
public:
    UniquePtr() { std::cout << "调用构造函数" << std::endl; }
    ~UniquePtr() { std::cout << "调用析构函数" << std::endl; }
    void print() { std::cout << "调用print()函数" << std::endl; }
};

int main() {
    std::unique_ptr ptr(new UniquePtr());
    ptr->print();

    return 0;
}

二、无法进行复制构造和赋值操作


int main() {
    std::unique_ptr ptr(new UniquePtr());
    std::unique_ptr ptr1(ptr); //报错
    std::unique_ptr ptr2 = ptr; //报错
   
    return 0;
}

三、可以进行移动构造和移动赋值操作

unique_ptr虽然没有拷贝和赋值操作,但却提供了一种移动机制来将指针的所有权从一个unique_ptr转移给另一个unique_ptr。如果需要转移所有权,可以使用std::move()函数。


int main() {
    std::unique_ptr ptr(new UniquePtr());
    std::unique_ptr ptr2 = std::move(ptr); //转移所有权
    std::unique_ptr ptr3(std::move(ptr));
   
    return 0;
}

四、可以通过函数返回值返回unique_ptr


#include 
#include 

class UniquePtr {
public:
    UniquePtr() { std::cout << "调用构造函数" << std::endl; }
    ~UniquePtr() { std::cout << "调用析构函数" << std::endl; }
    void print() { std::cout << "调用print()函数" << std::endl; }
};

std::unique_ptr return_unique_ptr() {
    std::unique_ptr ptr(new UniquePtr());

    return ptr;
}

int main() {
    std::unique_ptr ptr = return_unique_ptr();
   
    return 0;
}

参考

https://www.cnblogs.com/vivian187/p/14812987.html
链接:https://blog.csdn.net/L1413999/article/details/132092160
https://blog.csdn.net/wang13342322203/article/details/80807053
https://blog.csdn.net/Mr_xiao_1/article/details/80219162
https://www.cnblogs.com/TechNomad/p/17484529.html

你可能感兴趣的:(嵌入式数据结构C++工具链,c++,学习,java)