C++类和对象(上)

简介: C++类和对象

C++类和对象


封装


类的封装性


封装:


  • 把变量和函数合成一个整体
  • 对变量和函数进行访问控制
  • 类内部没有访问权限之分,所有成员可以相互访问
  • 在类的外部,访问权限才有意义。public protected private
  • 在累的外部值public修饰的成员才能被访问,在没有涉及继承与派生时,privateg和protected是同等级的,外部不允许访问。


类的初识

class 类名 // 私有,抽象概念, 系统不会给其分配空间
{
public:     // 公有  类的外部可访问
protected:  // 保护  类的外部不可访问
private:  // 私有  类的外部不可访问
};
#include <iostream>
#include <string>
#include <iostream>
using namespace std;
class Person
{
public:
    void say()
    {
        money = 100;
        cout << "我是傻逼" << endl;
    }
protected:
    int age;
private:
    int money;
};
int main(int argc, char* argv[])
{
    Person p;
    // 虽然不可访问私有数据,但是可以使用公有方法访问私有变量
    p.say();
    return 0;
}
  • stuct和class的区别是struct的默认权限是公有的,而class默认权限是私有的。


将成员变量设为私有,可以赋予客户端访问数据的一致性。如果成员变量不是public,客户端唯一能够访问对象的方法就是通过成员函数。如果类中所有public权限的成员都是函数,客户在访问类成员的时候只会访问默认函数,不需要考虑访问成员需不需要添加()


可以细微划分访问控制,使用成员函数可以使得我们对变量的控制处理更加精细。如果我们让所有成员变量为public,每个人都可以读写它。如果我们设置为private,我们可以实现不准访问,只读访问,读写访问,设置可以写出只写访问。


构造和析构


构造函数和析构函数,这两个函数会被编译器自动调用,构造函数完成对象的初始化动作,析构函数在对象结束的时候完成清理工作。

如果你不提供构造函数和析构函数,编译器会给你增加默认的操作,但是默认操作不会做任何操作。


构造函数是创建对象时为成员属性赋值,构造函数由编译器自动调用,无需手动调用。析构函数主要用于对象销毁的时候自动调用,执行一些清理操作


构造和析构函数定义


构造函数名和类名相同,没有返回值,但是可以有参数 ClassName(){}


析构函数在类名前加~,没有返回值,不能重载,不能有参数 ~ClassName(){}

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
class Person
{
public:
    Person()
    {
        cout << "无参构造函数" << endl;
    }
    Person(int num)
    {
        this->num = num;
        cout << "有参数构造函数" << endl;
    }
    Person(const Person &other)
    {
        cout << "拷贝构造函数" << endl;
    }
    ~Person()
    {
        cout << "析构函数" << endl;
    }
private:
    int num;
};
int main(int argc, char* argv[])
{
    Person p;
    Person p1(1);
    return 0;
}

构造的分类以及调用


  • 构造函数的分类

按照参数类型:无参构造和有参构造

按照类型分类:普通构造和拷贝构造


  • 构造函数的调用

无参构造的调用形式:

Person p;             // 隐式调用
Person p1 = Person(); // 显示调用
Person();             // 匿名对象调用


有参构造的调用形式:

Person p(1);           // 隐式调用
Person p1 = Person(1); // 显示调用
Person p2 = 20;        // 隐式转换调用(只针对于有一个参数),尽量别用该方式
Person(1);             // 匿名对象调用


拷贝构造的调用形式:默认拷贝构造是浅拷贝,就对象初始化新对象的时候调用拷贝构造函数。

Person p;
Person p2 = Person(p);  // 显示调用
Person p3(p);           // 隐式调用
Person p4 = p;          // 使用等号隐式转换


注意: 构造函数和析构函数的顺序相反。

下方不会调用拷贝构造函数:

Person p(10);
Person p2;
p2 = p;


对于任何一个类,C++编译器至少会给我们写的类增加3个函数:

  • 默认构造
  • 默认析构
  • 默认拷贝构造


对类中的非静态成员属性简单的值拷贝,如果用户定义了拷贝构造,则C++不会再提供任何默认构造函数,如果用户提供了默认构造函数,C++不会提供默认无参构造函数,但是会提供拷贝构造函数。


因此我们在设计类的时候一般需要实现无参构造,有参构造,拷贝构造和析构函数。


深拷贝与浅拷贝


同一个对象之间可以赋值,使得2个对象的成员变量的值相同,两个对象仍然是独立的两个对象,这种情况被称为浅拷贝。一般情况下,浅拷贝没有任何副作用,但是当类中有指针,并且指针指向动态分配的内存空间的时候,析构函数做了动态内存释放的处理会导致内存重复释放的问题。

 浅拷贝,程序崩溃
#include <iostream>
#include <string>
#include <iostream>
using namespace std;
class Person
{
public:
    Person()
    {
        name = nullptr;
        num = 0;
        cout << "无参构造" << endl;
    }
    Person(char *name, int num)
    {
        this->name = (char *) calloc(1, strlen(name) + 1);
        if(this->name == nullptr)
        {
            cout << "构造失败" << endl;
        }
        strcpy(this->name, name);
        this->num = num;
        cout << "有参构造" << endl;
    }
    ~Person()
    {
        if(name != nullptr)
        {
            cout << "空间被释放" << endl;
            free(name);
            name = nullptr;
        }
        cout << "析构函数" << endl;
    }
    void show()
    {
        cout << "num:" << num << " name:" << name << endl;
    }
private:
    char *name;
    int num;
};
int main(int argc, char* argv[])
{
    Person p("aasda", 10);
    p.show();
    Person p2 = p;
    return 0;
}
 深拷贝,正常释放
#include <iostream>
#include <string>
#include <iostream>
using namespace std;
class Person
{
public:
    Person()
    {
        name = nullptr;
        num = 0;
        cout << "无参构造" << endl;
    }
    Person(char *name, int num)
    {
        this->name = (char *) calloc(1, strlen(name) + 1);
        if(this->name == nullptr)
        {
            cout << "构造失败" << endl;
        }
        strcpy(this->name, name);
        this->num = num;
        cout << "有参构造" << endl;
    }
    Person(const Person &other)
    {
        this->name = (char *) calloc(1, strlen(other.name) + 1);
        strcpy(this->name, other.name);
        this->num = other.num;
    }
    ~Person()
    {
        if(name != nullptr)
        {
            cout << "空间被释放" << endl;
            free(name);
            name = nullptr;
        }
        cout << "析构函数" << endl;
    }
    void show()
    {
        cout << "num:" << num << " name:" << name << endl;
    }
private:
    char *name;
    int num;
};
int main(int argc, char* argv[])
{
    Person p("aasda", 10);
    p.show();
    Person p2 = p;
    p2.show();
    return 0;
}

初始化列表与成员对象


构造函数和其他函数不同,除了有名字,参数列表函数体之外,还有初始化列表。

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
class Person
{
public:
    Person() : a(0), b(1), c(2)
    {
        cout << "无参构造" << endl;
    }
    Person(int a, int b, int c) : a(a), b(b), c(b)
    {
        cout << "无参构造" << endl;
    }
    void show()
    {
        cout << "a:" << a << " b:" << b << " c:" << c << endl;
    }
private:
    int a;
    int b;
    int c;
};
int main(int argc, char* argv[])
{
    Person p;
    p.show();
    Person p2(8, 9, 10);
    p2.show();
    return 0;
}

注意: 初始化列表只能在构造函数中使用


对象成员的初始化列表


在类中定义数据成员一般都是基本数据类型。但是当类中的成员也可以是对象,叫做对象成员。C++中对对象成员的初始化是非常重要的操作,当创建了一个对象的时候,C++编译器必须确保调用了所有子对象的构造函数。如果所有子对象有默认构造函数,编译器可以自动调用他们。但是如果子对象没有默认构造函数,或者想指定调用某个构造函数怎么办?初始化列表提供了对象成员的构造函数调用方式。

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
class Aaa
{
public:
    Aaa(int a) : aaa(a) {
        cout << "有参构造" << endl;
    }
    Aaa() {
        cout << "无参构造" << endl;
    }
    int getA(){ return  aaa; }
private:
    int aaa;
};
class Person
{
public:
    Person() : a(Aaa(5)) {}
    Person(int a) : a(Aaa(a)) {}
    void show()
    {
        cout << "a:" << a.getA() << endl;
    }
private:
    Aaa a;
};
int main(int argc, char* argv[])
{
    Person p;
    p.show();
    Person p2(8);
    p2.show();
    return 0;
}

explicit 关键字


C++提供了关键字explicit,表示禁止通过构造函数进行隐式转换。声明为explicit的构造函数不能在隐式转换中使用。

explicit是针对只有一个参数的构造函数,或者是除了第一个参数其他的都是默认值的多参数的构造函数。


动态对象的创建


在我们创建数组的时候总是需要提前预定数组长度,然后编译器分配预定长度的数组空间,在使用数组时,会有这样的问题,数组也许空间太大,也许空间太小。所以对于数组而言,如果能动态的分配大小空间最好不过了,多以C提供了动态分配内存函数malloc和free,可以在运行时候从堆中分配存储单元。然而这些函数在C++中不能很好的运行,因为他们不能帮我们完成对象的创建。


对象的创建


当创建一个C++对象的时候,会发生两件事:

  • 为对象分配内存空间。
  • 调用构造函数初始化内存。


如果我们使用C函数的话会有以下问题:


  • 程序员必须确定对象长度
  • malloc返回的是一个void指针,C++不允许将void指针赋值给其他指针,必须强转
  • malloc可能申请失败,必须判断返回值来保证内存分配成功
  • 用户在使用对象之前必须对它初始化,构造函数不能显示调用初始化,用户有可能会忘记调用初始化函数


总之,C的动态内存分配函数太复杂,于是C++中出现了new和delete来创建和销毁一个对象。

new operator


C++中解决动态分配方案就是把创建一个对象所需要的操作都结合在一个称为new的运算符里面,当使用new创建一个对象时,它就在堆里面为对象分配内存并调用构造函数完成初始化。


给基本对象申请空间
int main(int argc, char* argv[])
{
    int *p = new int(100);
    cout << *p << endl;
    int *p2 = new int[5]{1,2,3,4,5};
    cout << p2[0] << " " << p2[1] << endl;
    delete p;
    delete [] p2;
    return 0;
}


给对象申请空间
#include <iostream>
#include <string>
#include <iostream>
using namespace std;
class Person
{
public:
    Person() : a(5) {
        cout << "构造函数" << endl;
    }
    ~Person()
    {
        cout << "析构函数" << endl;
    }
    explicit Person(int a) : a(a) {
        cout << "有参构造函数" << endl;
    }
    void show()
    {
        cout << "a:" << a << endl;
    }
private:
    int a;
};
int main(int argc, char* argv[])
{
    // 申请空间,构造函数
    Person *p = new Person;
    // 析构函数,销毁空间
    delete p;
    Person *p2 = new Person[5];
    delete [] p2;
    Person *p3 = new Person[5]{Person(3),Person(2),Person(2),Person(2),Person(2)};
    delete [] p3;
    Person *p4 = new Person;
    void *p5 = p4;
    delete p5; // 不会析构
    return 0;
}


注意: 如果new没有加[] 释放的时候不用加[],如果new加了[]则delete也需要加[]。

delete 无法从void中寻找析构函数,释放的时候注意类型。


静态成员


在类定义中,成员包括成员变量和成员函数,这些成员可以使用static关键字声明为静态的,称为静态成员。不管这个类创建了多少个对象,静态成员只有一份拷贝,这个拷贝属于这个类的对象共享。


静态成员变量


在一个类中,如果将一个变量声明为static,这种成员称为静态成员变量。与一个的数据成员不同,无论建立多少个对象,该变量只有一份静态数据的拷贝。静态成员变量属于某个类所有的对象共享。静态变量是在编译期间就分配空间,对象还没创建的时候就已经分配了空间。


静态成员变量必须在类中声明,在类外定义。静态数据成员不属于某个对象,在为对象分配空间的时候不包括静态数据成员所占的控件。静态数据成员可以通过类名或者对象名来引用。


静态成员函数


针对于无法直接访问的private静态成员,可以使用静态成员函数访问。

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
class Person
{
public:
    Person() : a(5) {
        cout << "构造函数" << endl;
    }
    ~Person()
    {
        cout << "析构函数" << endl;
    }
    explicit Person(int a) : a(a) {
        cout << "有参构造函数" << endl;
    }
    void show()
    {
        cout << "a:" << a << endl;
    }
    static int getB()
    {
        return b;
    }
private:
    int a;
    static int b;
};
int Person::b = 20;
int main(int argc, char* argv[])
{
    cout << sizeof(Person) << endl;
    cout << Person::getB() << endl;
    return 0;
}


C++类和对象(中):https://developer.aliyun.com/article/1459441

目录
相关文章
|
9月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
5月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
158 0
|
5月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
249 0
|
7月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
289 12
|
8月前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
173 16
|
8月前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
8月前
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
8月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
476 6
|
8月前
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
|
9月前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)