C++学习-02<C++ 类和对象:类、构造函数、深/浅拷贝、初始化列表、const、友元>

抽象、多态、封装和数据隐藏、继承、代码重用

2-1  从结构体到类

2.1.1  定义

class 类名
{
public:

    成员1的数据类型 成员名1;
    成员2的数据类型 成员名2;
    成员3的数据类型 成员名3;
    ...
    成员n的数据类型 成员名n;
};

注:

  • 类的成员可以是变量,也可以是函数;
  • 类的成员变量也叫属性;
  • 类的成员函数也叫方法/行为,类的成员函数可以定义在类的外面;
例: 类的成员函数可以定义在类的外面
class 类名
{
    void func();  //函数声明
}

void 类名::func()  //函数定义
{

}
  • 用类定义一个类的变量叫 创建(或实例化)一个对象;
  • 类的成员变量和成员函数的作用域和生命周期与对象的作用域和生命周期相同;

2-2  类的访问权限

2.2.1 权限

  • 类的成员有三种访问权限:public、private、protected;

        在类的内部:无论成员被声明为public、还是private,都可以访问;

        在类的外部:只能访问public成员,不能访问private、protected成员;

  • 在一个类的定义中,private、public可以出现多次;
  • 结构体的成员缺省为public,类的成员缺省为private

2-3  简单使用类

  1. 类的成员函数可以直接访问该类其他的成员函数(可以递归);
  2. 类的成员函数可以重载;
  3. 类指针的用法与结构体指针用法相同;
  4. 类的成员可以是任意数据类型(类中枚举);
  5. 可以为类的成员指定缺省值(C++11标准);
  6. 类可以创建对象数组,就像结构体数组一样;
  7. 对象可以作为参数传递给函数,一般用传地址和传引用;
  8. 可以用new动态创建对象,用delete释放对象;
  9. 一般不直接访问(读和写)对象的成员,可以用成员函数;
  10. 对象一般不用memset()清空成员变量,可以写一个专用于清空成员变量的成员函数;
  11. 对类和对象用sizeof运算符意义不大,一般不用;
  12. 用结构体描述纯粹的数据,用类描述对象;
  13. 类分文件编写;

2-4  构造函数和析构函数

>>> 构造函数和析构函数不可用const关键字修饰

2.4.1 定义 

  • 构造函数:在创建对象时,自动的进行初始化工作;
  • 析构函数:在销毁对象前,自动的完成清理工作;

2.4.2  构造函数

语法:类名(){...}
  • 访问权限必须是public;
  • 函数名必须与类名相同;
  • 没有返回值,也不写void;
  • 可以有参数,可以重载,可以有默认参数;
  • 创建对象时会自动调用一次,不能手动调用;

2.4.3 析构函数

语法:~类名(){...}
  • 访问权限必须是public;
  • 函数名必须在类名前加 ~;
  • 没有返回值,也不写void;
  • 没有参数,不能重载;
  • 销毁对象前会自动调用一次,可以手动调用。

2.4.4 构造函数的细节

注意:

  • 如果没有提供构造/析构函数,编译器将提供空实现的构造/析构函数;
  • 如果提供了构造/析构函数,编译器将不提供空实现的构造/析构函数;
  • 创建对象的时候,如果重载了构造函数,编译器根据实参匹配相应的构造函数;
  • 创建对象的时候不要在对象名后面加空的圆括号,编译器误认为是声明函数。
  • 在构造函数名后面加括号和参数不是调用构造函数,是创建匿名对象;
  • 接受一个参数的构造函数允许使用赋值语法将对象初始化为一个值(会导致问题,不推荐)。
  • 用new/delete创建/销毁对象时,也会调用构造/析构函数;
  • 不建议在构造/析构函数中写太多代码,可以调用成员函数;
  • 除了初始化,不建议让构造函数做太多工作;
  • C++支持使用同一初始化列表;
//初始化
CLASSNAME name = {"xx",20};
CLASSNAME name {"xx",20};
CLASSNAME *name = new CLASSNAME{"xx",20};
  • 如果类的成员也是类,创建对象的时候,先构造成员类;销毁对象的时候,先析构成员类;

2-5  拷贝构造函数

2.5.1 介绍

  • 用一个已存在的对象创建新的对象,不会调用(普通)构造函数,而是调用拷贝构造函数;
class MYClass
{
    MYClass(){...};
    ~MYClass();
}

//调用

MYClass m1;
MYClass m2 = MYClass;
MYClass *m3 = new MYClass;
MYClass *m4 = new MYClass();

delete m3;
delete m4;
  • 如果类中没有定义拷贝构造函数,编译器将提供一个拷贝构造函数,它的功能是把已存在对象的成员变量赋值给新对象的成员变量;

2.5.2 语法

  • 用一个已存在的对象创建新的对象的语法:
类名 新对象名(已存在的对象名);
类名 新对象名= 已存在的对象名;

//如下例子:
/*
    第一行是g1函数的volg;
    第二行是g2.show()的volg;(说明数据来源于对象g1);
    g1、g2销毁时,分别调用了一次析构函数;
 
*/

  • 拷贝构造函数的语法:
类名(const 类名& 对象名){...}
  •  注意:
  1. 访问权限必须是public;
  2. 函数名必须与类名相同;
  3. 没有返回值,不写void;
  4. 如果类中定义了拷贝构造函数,编译器将不提供拷贝构造函数;
  5. 以值传递的方式调用函数时,如果实参为对象,会调用拷贝构造函数;
  6. 函数以值的方式返回对象时,可能会调用拷贝构造函数

如下说明: 

进入构造函数;
再触发拷贝构造函数;
gg赋值给g之后 gg销毁,第一次调用析构函数;
调用show函数;
g销毁调用第二次析构函数;

2-6  浅拷贝和深拷贝

2.6.1. 浅拷贝

指针a指向堆区的内存(0x00B3F),(浅拷贝:若将指针a的值赋给指针b),那么指针a和指针b将指向同一块内存(0x00B3F)。假设指针a和指针b分别是对象A和对象B的两个成员,将会产生两个问题:

① 其中一个对象修改了内存中的数据,会影响另一个对象;

②其中一个对象释放了内存,另一个对象的指针就成了野指针;

2.6.2 深拷贝

指针a指向内存(0x00B3F),(深拷贝:重新分配一块大小相同的内存让指针b指向新内存,再把指针a指向的内存中的数据拷贝到新内存中),拷贝后大家各自操作自己的指针和内存。

2-7  初始化列表

2.7.1 简介

构造函数的执行可以分成两个阶段:初始化阶段和计算阶段。

  • 初始化阶段先于计算阶段。
  • 初始化阶段:全部的成员都会在初始化阶段初始化,即使该成员没有出现在构造函数的初始化列表中;
  • 计算阶段:一般是指用于执行构造函数体内的赋值操作;

2.7.3 初始化列表

构造函数  除了 参数列表 和  函数体 之外,还可以有  初始化列表。

类名(形参列表):成员1(值1),成员2(值2)...,成员n(值n)
{
    ......
}

2.7.4 注意

  •  如果成员已经在初始化列表中,则不应该在构造函数中再次赋值;
  • 初始化列表的括号中可以是具体的值,也可以是构造函数的形参名,还可以是表达式;
  • 初始化列表于赋值有本质的区别,如果成员是类,使用初始化列表调用的是拷贝构造函数,而复制则是先创建对象(调用普通构造函数),然后再赋值;
  • 如果成员是类,初始化列表对性能略有提升;
  • 如果成员是常量和引用,必须使用初始化列表,因为他们只能在定义的时候初始化;
  • 如果成员是没有默认构造函数的类,则必须使用初始化列表;
  • 拷贝构造函数也可以有初始化列表;
  • 类的成员变量可以不出现在初始化列表中;

2-8  const 修饰成员函数

2.8.1 简介

  • 在类的成员函数后面加 const 关键字,表示在成员函数中保证不会修改调用对象的成员变量
  • 析构函数和构造函数不允许使用const;
void ClassName::func() const
{
    //
}

2.8.2 要点

  • mutable可以突破const的限制,被mutable修饰的成员变量,将永远处于可变的状态,在const修饰的函数中,mutable成员也可以被修改;
mutable string name;
  • 非const成员函数可以调用const成员函数和非const成员函数;
  • const成员函数不能调用非const成员函数;
  • 非const对象可以调用const修饰的成员函数和非const修饰的成员函数;
const ClassName class_name();  //常对象
  • const对象只能调用const修饰的成员函数,不能调用非const修饰的成员函数;

2-9  this指针

2.9.1 定义

  • 如果类的成员函数中涉及多个对象,在这种情况下需要使用this指针。

2.9.2 说明

  • this指针存放了对象的地址,它被作为隐藏参数传递给了成员函数,指向调用成员函数的对象(调用者对象);
  • 每个成员函数(包括构造函数和析构函数)都有一个this指针,可以用它访问调用者对象的成员。(可以解决成员变量名与函数形参相同的问题);
  • *this可以表示整个对象;
  • 如果在成员函数的括号后面使用const,那么将不能通过this指针修改成员变量;
class CLASSNAME
{

public:
    int m_grade;
    string m_name;
    
    //构造函数 析构函数
    CLASSNAME(const string &name,int grade){m_name = name,m_grade = grade};
    ~CLASSNAME(){};

    //成员函数
    const CLASSNAME& pk(const CLASSNAME& g) const
    {
        if(g.m_grade < m_grade ) return g;
        return *this;  //返回自己
    }


    int aa; //通常成员变量命名方式为:m_aa或aa_,此处举例
    void func(int aa)
    {
        this->aa = aa;  // 正确
        aa = aa;        // 错误,均为形参aa
    }

};

int main()
{
    CLASSNAMEg1("XX",1),g2("CC",2),g3("VV",3);
    const CLASSNAME& g = g1.pk(g2).pk(g3);
    
    //通过上述代码得到 g1 ;
}

2-10 静态成员

2.10.1 说明

类的静态成员:静态成员变量、静态成员函数;

  • 用静态成员可以变量实现多个对象之间的数据共享,比全局变量更安全;
  • 用static关键字把类的成员变量声明为静态,表示它在程序中(不仅是对象)是共享的;
  • 静态成员变量不会在创建对象的时候初始化,必须在程序的全局区用代码清晰的初始化(用范围解析运算符::);
  • 静态成员使用类名加  ::  就可以访问,不需要创建类对象;
  • 如果把类的成员声明为静态的,就可以它与类的对象独立开来(静态成员不属于对象,属于整个程序);
  • 静态成员变量在程序中只有一份(生命周期与程序运行期相同,存放在静态存储区的),不论是否创建了类的对象,也不论创建了多少个类的对象;
  • 静态成员函数只能访问静态成员,不能访问非静态成员;
  • 静态成员函数中没有this指针;
  • 非静态成员可以访问静态成员;
  • 私有静态成员变量在类外无法访问;
  • const静态成员变量可以在的定义类的时候初始化;

2-11  简单对象模型

2.11.1 简介

1. C++类中有两种数据成员:nonstatic、static;三种函数成员:nonstatic、static、virtual;

2. 对象 的内存大小包括:

  • 所有非静态数据成员的大小;
  • 由内存对齐而填补的内存大小;
  • 为了支持virtual成员而产生的额外负担;

3. 静态成员 变量属于类,不计算在对象大小之内;

4. 成员函数 是分开存储的,不论对象是否存在都占用存储空间,在内存中只有一个副本,也不计算在对象大小之内;

5. 用空指针可以访问没有用到的this指针的非静态成员函数;

6. 对象的地址是第一个非静态成员变量的地址。如果类中没有非静态成员变量,编译器会隐含的增加一个1字节的占位成员;

2-12  友元

2.12.1 简介

  • 如果要访问类的私有成员变量,调用类的公有成员函数是唯一的办法,而类的私有成员函数则无法访问
  • 友元提供了另一访问私有成员的方案。友元有三种:友元全局函数、友元类、友元成员函数;

2.12.2 友元全局函数

  • 在友元全局函数中,可以访问另一个类的所有成员;

2.12.3 友元类

在友元类所有成员函数中,可以访问另一个类的所有成员;

友元类注意事项(仅看类中的声明):

  • 友元关系不能被继承;
  • 友元关系是单向的,不具备交换性;
class CLASSNAME
{

//声明友元函数
friend int main();
friend void func();  
//声明友元类
friend class CLASSNAME2;

public:
    string m_name;
    CLASSNAME(){m_name = "XX";m_age = 12;}
    void showname(){cout << m_name << endl;}
private:
    int m_age;
    void showage()const{cout << m_age << endl;}
};
class CLASSNAME2
{

public:
void func(const CLASSNAME& g)
{
    cout<< g.m_name;
    cout << g.m_age;
    g.showage();
}
};
/*示例1
void func()
{
    CLASSNAME g;
    g.showname();
    g.showage();  //没有声明友元就是错误的
}
*/
int main()
{
/*
示例1:

    CLASSNAME g;
    g.showname();
    g.showage();  //没有声明友元就是错误的

    func();
*/

/* 

示例2:

    CLASSNAME g;
    CLASSNAME g2;
    g2.func();

*/
}

2.12.4 友元成员函数

  • 在友元类成员函数中,可以访问另一个类的所有成员;
  • 如果要把 CLASSNAME2 的某成员函数声明为CLASSNAME中的友元,声明和定义顺序如下:
class CLASSNAME;  //前置声明
class CLASSNAME2{1. 函数声明 2. 其他代码};
class CLASSNAME{1. 声明友元函数  2. 其他代码};

//友元成员函数的定义
void CLASSNAME2::func(CLASSNAME &g)
{
    ....;
}
  • 示例: 
class CLASSNAME; //前置声明


class CLASSNAME2
{
public:
void func1(const CLASSNAME& g);
//void func2(const CLASSNAME& g);
void func3(const CLASSNAME& g);
};


class CLASSNAME
{

//声明友元函数
friend void CLASSNAME2::func1(const CLASSNAME& g);
//friend void CLASSNAME2::func2(const CLASSNAME& g);

public:
    string m_name;
    CLASSNAME(){m_name = "XX";m_age = 12;}
    void showname(){cout << m_name << endl;}
private:
    int m_age;
    void showage()const{cout << m_age << endl;}
};
void CLASSNAME2::func1(const CLASSNAME& g)
{
    cout << g.m_age; 
}
void CLASSNAME2::func2(const CLASSNAME& g)
{
    //cout << g.m_age;
}
void CLASSNAME2::func3(const CLASSNAME& g)
{
    cout << g.m_name;
}
int main()
{
    CLASSNAME g;
    CLASSNAME g2;
    g2.func1();   //私有,但是设置友元,访问正确
    //g2.func2(); //私有,访问报错
    g2.func3();   //公有,访问正确
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值