栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++类和对象-对象特性

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++类和对象-对象特性

目录

构造函数 和 析构函数

        构造函数

        析构函数

构 造 函 数 的 分 类 及 调 用

        有参构造

        无参构造函数

        普通构造函数

        拷贝构造函数

        括号法

        显示法

        隐式转换法

拷 贝 构 造 函 数 的 调 用 时 机

        使用一个已经创建完毕的对象来初始化一个新对象

        值传递的方式给函数参数传值

        值方式返回局部变量

构 造 函 数 调 用 规 则

        构造函数调用规则

        用户定义有参构造函数,C++不再提供无参构造

        用户定义有参构造函数,编译器会提供拷贝构造

        用户只定义拷贝构造函数,C++ 就不提供其他构造函数

深 拷 贝 与 浅 拷 贝

        浅拷贝

        深拷贝

初 始 化 列 表

        传统的初始化操作

        初始化列表初始化属性

 类 对 象 作 为 类 成 员

 静 态 成 员

        静态成员变量

        静态成员有两种访问方式

        类外访问不到私有的静态成员

        静态成员函数 

 对 象 模 型

 this 指 针

         this 指针的用途

        解决名称冲突 

         返回对象本身

空 指 针 调 用 成 员 函 数

 const 修 饰 成 员 函 数


构造函数 和 析构函数

对象的初始化和清理

  • 电子产品的出厂设置,还原出厂设置
  • C++中的面向对象源于生活,每个对象也都有初始设置以及对象销毁前的清理数据设置

对象的初始化和清理

  • 一个对象或者变量没有初始化,后果是未知的
  • 使用完一个对象或一个变量,没有及时清理,也会造成安全问题
     

C++使用 构造函数 和 析构函数 解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作,对象的初始化和清理工作是编译器强制要求做的,如果不提供构造函数和析构函数,编译器会提供编译器提供的构造函数和析构函数,但是是空实现

构造函数

作用:创建对象时为对象成员属性赋值初始化,构造函数由编译器自动调用,无需手动调用

-

构造函数语法:类名 () {} 

-

  1. 构造函数,没有返回值也不写 void
  2. 函数名称与类名相同
  3. 构造函数可以有参数,因此可以发生重载
  4. 程序调用对象时会自动调用,无需手动调用,而且只会调用一次
#include
using namespace std;

// 对象的初始化和清理
class Person
{
public:
    // 构造函数
    Person()
    {
        cout << "Person 构造函数的调用" << endl;
    }
};

void test01()
{
    Person p1; // 只创建对象就可以调用构造函数
}

int main()
{
    test01();

    system("pause");
    return 0;
}

析构函数

作用:对象销毁前系统自动调用,执行清理工作

-

析构函数语法:~ 类名() {} 

-

  1. 析构函数,没有返回值,也不写 void
  2. 函数名称与类名相同
  3. 析构函数不可以有参数,因此不可以发生重载
  4. 程序在对象销毁前会自动调用析构,无需手动调用,而且只会调用一次
#include
using namespace std;

// 对象的初始化和清理
class Person
{
public:
    // 析构函数
    ~Person()
    {
        cout << "Person 析构函数的调用" << endl;
    }
};

void test01()
{
    Person p1; // 栈上的数据,test01 函数执行完后系统释放,就会调用析构函数
}

int main()
{
    test01();

    system("pause");
    return 0;
}

 如果在主函数内创建类,则析构函数的内容会关闭程序后才执行

 原因:创建的类在执行 return 0 ;之前,system("pause");之后,p1才会被释放,析构函数才执行

构 造 函 数 的 分 类 及 调 用

两种分类方式:

  • 按参数分为:有参构造 和 无参构造
  • 按类型分为:普通构造 和 拷贝构造

三种调用方式:

  •  括 号 法 
  •  显 示 法 
  •  隐 式 转 换 法 
#include
#include
using namespace std;

class Person
{
public:
    // 普通构造函数
    // 无参 (默认构造函数)
    Person()
    {
        cout << "Person 无参构造函数调用" << endl;
    }
    // 有参
    Person(int a)
    {
        age = a;
        cout << "Person 有参构造函数调用" << endl;
    }

    // 拷贝构造函数 不能改变本体
    Person(const Person& p)
    {
        // 将传入的 Person 的所有属性,拷贝到 该类中的 Person
        age = p.age;
        cout << "Person 拷贝构造函数调用" << endl;
    }

    // 析构函数
    ~Person()
    {
        cout << "Person 析构函数调用" << endl;
    }
    int age;
};

// 调用
void test01()
{
    // 括号法 
    Person p1;              // 无参构造函数调用
    Person p2(10);          // 有参构造函数调用
    Person p3(p2);          // 拷贝构造函数调用

    // 显示法
    Person p5;              // 无参构造函数
    Person p6 = Person(10); // 有参构造函数
    Person p7 = Person(p5); // 拷贝构造函数

    // 隐式转换法
    Person p8 = 10;         // 相当于 Person p4 = Person(10);
    Person p9 = p8;         // 拷贝构造
}
int main()
{
    test01();
    system("pause");
    return 0;
}

有参构造

 构造函数里含有参数

// 有参
Person(int a)
{
    age = a;
    cout << "Person 有参构造函数调用" << endl;
}

无参构造函数

 构造函数里不含有参数

// 无参 (默认构造函数)
Person()
{
    cout << "Person 无参构造函数调用" << endl;
}

普通构造函数

 没有参数、一个或多个参数传入,参数类型非类

// 普通构造函数
Person()
{
    cout << "Person 无参构造函数调用" << endl;
}
Person(int a)
{
    age = a;
    cout << "Person 有参构造函数调用" << endl;
}

拷贝构造函数

 拷贝不能改变本体,所以需要对传入参数加 const 修饰,防止对其进行修改,&引用减少内存消耗 

// 拷贝构造函数 不能改变本体
Person(const Person& p)
{
    // 将传入的 Person 的属性,拷贝到 该类中的 Person
    age = p.age;
    cout << "Person 拷贝构造函数调用" << endl;
}

括号法

注意事项:

调用默认构造函数(无参)时,不要加  ()

Person  p4();  // 编译器会认为这是一个函数声明(主函数内也可以进行函数声明)

Person  p4();  // Person 函数返回类型,p4 函数名,() 无参数 

// 括号法
Person p1;      // 无参构造函数调用
Person p2(10);  // 有参构造函数调用
Person p3(p2);  // 拷贝构造函数调用

显示法

注意事项:

1、Person(10);  // 为匿名对象

        特点:当前执行行结束后系统会立即回收匿名对象

 2、不要利用拷贝函数初始化匿名对象

        Person(p3); // p3 重定义

        编译器认为 Person(p3) === Person p3;  //  类声明

// 显示法
Person p5;              // 无参构造函数
Person p6 = Person(10); // 有参构造函数
Person p7 = Person(p5); // 拷贝构造函数

隐式转换法
// 隐式转换法
Person p8 = 10; // 相当于 Person p4 = Person(10);
Person p9 = p8; // 拷贝构造

拷 贝 构 造 函 数 的 调 用 时 机

C++中拷贝构造函数调用时机有三种情况

  • 使用一个已经创建完毕的对象来初始化一个新对象
  • 值传递的方式给函数参数传值
  • 以值方式返回局部对象
#include
using namespace std;

class Person
{
public:
    // 构造函数
    Person()
    {
        cout << "Person 无参构造函数调用" << endl;
        m_Age = 0;
    }
    // 构造函数重载
    Person(int age)
    {
        cout << "Person 有参构造函数调用" << endl;
        m_Age = age;
    }
    // 拷贝构造函数
    Person(const Person& p)
    {
        cout << "Person 拷贝构造函数调用" << endl;
        m_Age = p.m_Age;
    }
    // 析构函数
    ~Person()
    {
        cout << "Person 析构函数调用" << endl;
    }
    int m_Age;
};

// 拷贝构造函数调用时机

// 1、使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
    Person p1(20); // 调用有参构造函数
    Person p2(p1); // 调用拷贝构造函数

    cout << "p2 的年龄:" << p2.m_Age << endl;
}

// 2、值传递的方式给函数参数传值
void doWork(Person p) // 拷贝临时副本
{

}
void test02()
{
    Person p;
    doWork(p);
}

// 3、值方式返回局部变量
Person doWork2()
{
    Person p1;
    return p1; // 返回的是值 
}
void test03()
{
    Person p = doWork2();
}

int main()
{
    test01();
    test02();
    test03();
    system("pause");
    return 0;
}

使用一个已经创建完毕的对象来初始化一个新对象
// 1、使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
    Person p1(20); // 调用有参构造函数
    Person p2(p1); // 调用拷贝构造函数

    cout << "p2 的年龄:" << p2.m_Age << endl;
}

值传递的方式给函数参数传值
// 2、值传递的方式给函数参数传值
void doWork(Person p) // 拷贝临时副本
{

}
void test02()
{
    Person p;
    doWork(p);
}

值方式返回局部变量
// 3、值方式返回局部变量
Person doWork2()
{
    Person p1;
    return p1; // 返回的是值
}
void test03()
{
    Person p = doWork2();
}

构 造 函 数 调 用 规 则

C++编译器默认情况下至少给一个类添加 3 个函数(自动)

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则
  • 如果用户定义有参构造函数,C++ 就不提供默认无参构造,但会提供默认拷贝构造
  • 如果用户定义拷贝构造函数,C++ 就不提供其他构造函数

用户定义有参构造函数,C++不再提供无参构造

用户定义有参构造函数,编译器会提供拷贝构造

用户只定义拷贝构造函数,C++ 就不提供其他构造函数

深 拷 贝 与 浅 拷 贝

浅拷贝

简单的赋值拷贝操作,如编译器提供的拷贝构造函数

问题:堆区的内存重复释放(析构函数释放堆区开辟数据)

#include
using namespace std;

class Person {
public:
    Person() {
        cout << "Person 默认构造函数调用" << endl;
    }
    Person(int age, int height) {
        Age = age;
        Height = new int(height);
        cout << "Person 有参构造函数调用" << endl;
    }
    ~Person() {
        // 析构函数
        // 将堆区开辟的数据释放
        if (Height != NULL) {
            delete(Height); // 释放指针
            Height = NULL;  // 指针置空
        }  
        cout << "Person 析构函数调用"     << endl;
    }
    int Age;
    int* Height; // 身高指针变量
};

void test01(){
    Person p1(18, 180);
    cout << "p1 的年龄:" <<  p1.Age    << endl;
    cout << "p1 的身高:" << *p1.Height << endl;

    // 浅拷贝操作
    Person p2(p1); 
    cout << "p2 的年龄:" <<  p2.Age    << endl;
    cout << "p2 的身高:" << *p2.Height << endl;
}

int main(){
    test01();
    system("pause");
    return 0;
}

 需要深拷贝进行解决

 -

深拷贝

在堆区重新申请空间,进行拷贝操作

浅拷贝的问题在于将指针全部完全拷贝过来,析构函数无法区分指向内容是否存在

自己实现拷贝构造函数解决浅拷贝问题

#include
using namespace std;

class Person {
public:
    Person() {
        cout << "Person 默认构造函数调用" << endl;
    }
    Person(int age, int height) {
        Age = age;
        Height = new int(height);
        cout << "Person 有参构造函数调用" << endl;
    }
    Person(const Person& p) {
        Age = p.Age;
        //Height = p.Height; // 编译器默认实现
        // 深拷贝
        Height = new int(*p.Height); // 自己实现
    }
    ~Person() {
        // 析构操作
        // 将堆区开辟的数据释放
        if (Height != NULL) {
            delete(Height); // 释放指针
            Height = NULL;  // 指针置空
        }  
        cout << "Person 析构函数调用"     << endl;
    }
    int Age;
    int* Height; // 身高指针变量
};

void test01(){
    Person p1(18, 180);
    cout << "p1 的年龄:" <<  p1.Age    << endl;
    cout << "p1 的身高:" << *p1.Height << endl;
    // 浅拷贝操作
    Person p2(p1); 
    cout << "p2 的年龄:" <<  p2.Age    << endl;
    cout << "p2 的身高:" << *p2.Height << endl;
}

int main(){
    test01();
    system("pause");
    return 0;
}

初 始 化 列 表

作用:C++提供了初始化列表的语法,用来初始化属性

-

// 初始化列表初始化属性
Person() :A(10), B(20), C(30)
{

}

语法: 构造函数 () : 属性1 (值 1) ,  属性2 (值 2) ,  ... { } 

传统的初始化操作
// 传统初始化操作 有参构造函数
Person(int a, int b, int c) {
    A = a; B = b; C = c;
}

初始化列表初始化属性

 给定初始化

// 初始化列表初始化属性
Person() :A(10), B(20), C(30)
{

}

自己初始化 

// 初始化列表初始化属性
Person(int a, int b, int c) :A(a), B(b), C(c)
{

}

 类 对 象 作 为 类 成 员

C++类中的成员可以是另一个类的对象,该成员为 对象成员

-

class A
{
    
}
class B
{
    A a;
}

B 类中有对象 A 作为成员,A 为对象成员

  A 和 B 的 构造 和 析构 谁先 ? 

先 A 进行构造,然后再 B 进行构造,先构造类对象再构造自身

先 B 析构,再 A 析构,先析构自身再析构类对象

#include
#include
using namespace std;

class Phone {
public:
    Phone(string pName) :m_PName(pName) {
        cout << "Phone  的构造函数" << endl;
    }
    ~Phone() {
        cout << "Phone  的析构函数" << endl;
    }
    string m_PName;
};
class Person {
public:
    //                                               Phone m_Phone = pName; // 隐式转换法
    Person(string name, string pName) :m_Name(name), m_Phone(pName) {
        cout << "Person 的构造函数" << endl;
    }
    ~Person() {
        cout << "Person 的析构函数" << endl;
    }
    string m_Name;
    Phone  m_Phone;
};
void test01() {
    Person p("李华", "HUAWEI");
    cout << p.m_Name <<  p.m_Phone.m_PName << endl;
}
int main() {
    test01();
    system("pause");
    return 0;
}

 静 态 成 员

静态成员就是再成员变量和成员函数前加上关键字 static ,称为静态成员

-

静态成员分类:

-

1、静态成员变量

  • 所 有 对 象 共 享 同 一 份 数 据
  • 在 编 译 阶 段 分 配 内 存
  • 类 内 声 明 ,类 外 初 始 化   

2、静态成员函数 

  • 所 有 对 象 共 享 一  个 函 数
  • 静 态 成 员 函 数 只 能 访 问 静 态 成 员 变 量

静态成员变量

所有对象共享一个数据

#include
#include
using namespace std;

class Person {
public:
    void setName(string name) {
        m_Name = name;
    }
    // 静态成员函数只能访问静态成员变量
    void setSex(string sex) {
        //m_sex = sex;
    }
    // 所有对象共享数据
    // 编译阶段分配内存
    // 类内声明,类外初始化
    
    // 类内声明
    static string m_Name; 
    string m_sex;
};

// 类外初始化
string Person::m_Name = "李华";

void test01() {
    Person p1;
    cout << "p1 的姓名:" << p1.m_Name << endl;
    Person p2;
    p2.m_Name = "张三";
    cout << "p2 的姓名:" << p2.m_Name << endl;
    cout << "p1 的姓名:" << p1.m_Name << endl;

}
int main() {
    test01();
    system("pause");
    return 0;
}

 静态成员不属于某一个对象

静态成员有两种访问方式
  • 通过 对象 进行访问
cout << p.m_Name << endl; // 通过对象进行访问

  • 通过 类名 进行访问 
cout << Person::m_Name << endl; // 通过类名的方式访问

类外访问不到私有的静态成员

静态成员函数 

 静态成员函数不可访问非静态的成员属性,无法区分是哪个对象的属性

静态成员函数也是有访问权限的,放到 private 权限下

 对 象 模 型

类内的 成员变量 和 成员函数 分开存储

只有 非静态成员变量才属于类的对象上

空对象占用 1 个字节

C++编译器会给每个空对象分配 1 个字节空间,为了区分空对象占内存的位置

所以每个空对象也都有独一无二的内存地址

 非静态成员变量属于类的对象

 静态成员变量不属于类的对象上

 非静态成员函数不属于类的对象上

静态成员函数不属于类的对象上 

 this 指 针

每一个非静态成员函数只会诞生一份函数实例,多个同类型的对象会公用一块代码

-

这一块代码如何区分那个对象调用自身?

-

C++通过提供特殊的对象指针,this 指针。

this 指针指向被调用的成员函数所属的对象

this 指针是隐含每一个非静态成员函数内的一种指针

-

this 指针不需要定义,可以直接使用

 this 指针的用途
  • 当形参和成员变量 同名时,可用 this 指针来区分
  • 在类的非静态成员函数中返回对象本身,可以使用 return *this

解决名称冲突 

方法1: 名称需要规范

成员属性要和传入的形参要有区别

 方法2:this 指针指向被调用的成员函数所属的对象 ,this 指向 p1

 返回对象本身
#include
#include
using namespace std;

class Person {
public:
    Person(int age) {
        this->age = age;
    }
    // 引用作为函数返回类型
    Person& PersonAddAge(const Person& p) {
        this->age += p.age;
        // this 指向 p2 的指针,*this 指向的就是 p2 这个对象本体
        return *this;
    }
    int age;
};
// 1、解决名称冲突
void test01() {
    Person p1(19);
    cout << "p1 的年龄:" << p1.age << endl;
}
// 2、返回对象本身用*this
void test02() {
    Person p1(10);
    Person p2(20);
    // 链式编程
    p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
    cout << "p2 的年龄:" << p2.age << endl;

}
int main() {
    //test01();
    test02();
    system("pause");
    return 0;
}

 如果返回类型不是引用而是值,则每次都会创建新的数据

空 指 针 调 用 成 员 函 数

C++中空指针可以调用成员函数,但需注意是否使用 this 指针

-

有 this 指针,需要加以判断保证代码的健壮性

#include
using namespace std;

class Person {
public:
    void showClassName() {
        cout << "Person 类" << endl;
    }
    void showPersonAge() {
        cout << "age = " << m_Age << endl;
    }
    int m_Age;
};

void test01() {
    Person* p = NULL; 
    p->showClassName();
    p->showPersonAge(); // error
}
int main() {
    test01();
    system("pause");
    return 0;
}

 类的属性前都默认加了 this->

void showPersonAge() {
    cout << "age = " << this->m_Age << endl;
}

this 是一个空指针,this 没有指向一个确切的值,更访问不到成员

所以为了防止这种情况(空指针对属性进行访问),要加以判断

void showPersonAge() {
    if (this == NULL) {
        return;
    }
    cout << "age = " << this->m_Age << endl;
}

 const 修 饰 成 员 函 数

常函数:

  • 成员函数后面加 const 后,我们称这个函数为 常函数    

        在成员函数后面加上 const 修饰的是 this 指针指向

         const  Person*  const  this; 

class Person {
public:
    void showPerson() const  
    {
    
    }
}
  • 常函数内不可以修改成员属性

  • 成员属性声明时加关键字 mutable 后,在常函数中依然可以修改

常对象:

  • 声明对象前面加 const 称该对象为 常对象

  • 常对象只能调用常函数

 

this 本质是 常量指针 

-

const 类名* this; 

#include
using namespace std;

class Person {
public:
    // const Person* const this;
    // 在成员函数后面加上 const 修饰的是 this 指向
    void showPerson() const
    {
        // this 本质是 指针常量,指向不可以修改
        //this = NULL;//分配到“this”(记时错误)	const修饰成员函数
        // this 指向 p
        //this->m_A = 100;//表达式必须是可修改的左值
        this->m_B = 100;  // √ 常函数中也可以修改
    }
    void showPerson2() {
        cout << "非常函数调用" << endl;
    }
    int m_A;
    mutable int m_B; // 特殊变量,即使在常函数中也可以对这个值进行修改
};

void test01() {
    Person p;
    p.showPerson();
}

void test02() {
    // 在对象前加上 const,变为常对象
    const Person p;
    //p.m_A = 100; // 表达式必须为可修改的左值
    p.m_B = 100;   // √ 常对象中也可以修改

    // 常对象调用常函数
    p.showPerson(); // √
    // 常对象不可以调用非常函数
    //p.showPerson2();// 对象含有与成员 函数 "Person::showPerson2" 不兼容的类型限定符	const修饰成员函数

}

常对象只能调用常函数的理解:

如果常对象可以调用非 常函数,则可以通过调用非 常函数对类的属性进行修改,而常函数对属性进行修改的操作是不允许的

-

常对象和常函数本来都不可以修改成员属性,但如果常对象可以调用常函数以外的函数,那就可以借助这个操作来对成员属性进行修改,但这是不允许的,所以常对象只能调用常函数

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1004627.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号