类和对象

1. 类和对象

1.1 声明类

类包含:属性(数据),方法(类成员的函数)
使用关键字class声明,如声明一个人类

//使用关键字class声明一个人类
class Human
{
    //数据属性 data attributes
    string Name;
    string DateOfBirth;
    string PlacwOfBirth;

    //方法 method
    void Talk(string TextToTalk);
    void IntroduceSelf();
}

1.2 实例化对象

实例化对象的语法:

//实例化对象
Human Tom;    //在栈上实例化一个对象
Human *pAHuman = new Human();        //在堆上实例化一个对象
delete pAHuman;        //删除对象

1.3 使用句点运算符访问类成员

//使用句点运算符访问类成员
// 访问类成员数据和类成员函数
Human Tom;
Tom.DateOfBirth="1990";
Tom.IntroduceSelf;
//指针pTom形式
Human* pTom = new Human();
(*pTom).IntroduceSelf();

1.4 使用指针运算符(->)访问成员

//使用指针运算符(->)访问成员
//Human Tom;
//Human *Ptom = &Tom;
Human* pTom = new Human();
pTom->DataOfBirth = "1970";
pTom->IntroduceSelf();

2. 关键字public和private

public和private两个关键字能够控制类书香的访问和操纵方式。一般而言,需要用private修饰类成员数据,以保护数据不被暴力修改。面向对象编程中修改对象的数据需要优雅地使用类成员方法对数据进行修改。

//一个包含private和public修饰的类
class Human
{
private:
    //私有成员数据
    int Age;
    string name;
public:
    int GetAge()
    {
         return Age;
    }
    int SetAge(int InputAge)
    {
        Age = InputAge;
    }
//...其他成员...
};

2.1 private关键字实现数据抽象

private关键字的特点:private指定了不能从类外访问的信息(成员数据和成员函数),对外界隐藏类使用者无需知道的东西

#include<iostream>
using namespace std;
class Human
{
private:
    //private member data
    int Age;
public:
    void SetAge(int InputAge)
    {
        Age = InputAge;
    }
    int GetAge()
    {
        if(Age > 30)
        return (Age - 2);
        else
            return Age;
    }
};

int main()
{
    Human FirstMan;
    FirstMan.SetAge(45);
    
    Human FirstWoman;
    FirstWoman.SetAge(23);
    
    cout << "Age of FirstMan "<< FirstMan.GetAge() <<endl;
    cout << "Age of FirstWoman " << FirstWoman.GetAge() <<endl;
    return 0;
}

3. 构造函数

构造函数是一种特殊的方法,在创建对象时使用。和普通的函数一样,可以被重载。

3.1 声明和实现构造函数

构造函数的名称和类名相同

//声明构造函数
class Human
{
public:
    Human()
    {
        // 构造函数的代码
    }
};

//在类外定义构造函数
class Human
{
public:
    Human();
};
//类外提供构造函数的实现
Human::Human()
{
    //构造函数的代码
}

3.2 何时使用构造函数

构造函数在创建对象时被调用,构造函数可将成员变量(int, string, 指针)初始化为已知值。

3.3 重载构造函数

构造函数可以被重载
可以选择不实现默认构造函数,从而要求实例化对象时必须提供某些参数,以使用重载的构造函数。

//重载构造函数
class Human
{
public:
    Human()
    {
        //默认构造函数
    }
    Human(string HumansName)
    {
        //重载构造函数
    }
};

3.4 没有默认构造函数的类

如果没有默认构造函数,而提供重载的构造函数时,编译器不会生产默认构造函数。
这样可以使创建每个对象时,都必须提供某些对象的参数。

3.5 带默认值的构造函数参数

//带默认值的构造函数参数
class Human
{
private:
    string Name;
    int Age;
public:
    // 重载构造函数
    Human(string HumansName, int HumansAge = 25)
    {
        Name = HumansName;
        Age = HumansAge;
        cout << "overload constructor creates " << Name;
        cout << " of age " << Age <<endl;
    }
    // other members
};

3.6 包含初始化列表的构造函数

//包含初始化列表的构造函数
class Human
{
private:
    string Name;
    int Age;
public:
    Human(string InputName, int InputAge):Name(InputName),Age(InputAge)
    {
        cout << "Constructor with parameters: ";
        cout << "Human " << Name <<", "<< Age << " years old"<<endl;
    }
};

4. 析构函数

析构函数和构造函数一样,也是一种特殊的函数。析构函数在对象销毁时自动被调用。
析构函数不能被重载。
如果忘记实现析构函数,则编译器会自动生成一个伪(dummy)析构函数并调用它,伪析构函数为空,不会释放动态分配的内存。

4.1 声明和实现析构函数

析构函数的声明

//析构函数的声明
class Human
{
public:
    ~Human()
    {
        //销毁成员数据的代码
    }
};

4.2 何时使用析构函数

当对象不在作用域或通过delete删除时,将调用析构函数对其进行销毁。析构函数成为了重置变量和释放内存的一种有效手段。

//用类封装一个c风格的字符串
#include<iostream>
using namespace std;

class MyString
{
private:
    char* Buffer;
public:
    // constructor
    MyString(const char* InitialInput)
    {
        if(InitialInput != NULL)
        {
            Buffer = new char [strlen(InitialInput)+1];
            strcpy(Buffer,InitialInput);
        }
        else
            Buffer = NULL;
    }
    // distructor
    ~MyString()
    {
        cout << "Invoking destructor, cleaning up! "<<endl;
        if(Buffer != NULL)
        {
            delete[] Buffer;
        }
    }
    int GetLength()
    {
        return strlen(Buffer);
    }
    
    const char* GetString()
    {
        return Buffer;
    }
};

int main()
{
    MyString SayHello("hello from string class");
    cout << "Length: " << SayHello.GetLength() << endl;
    cout << "content: " << SayHello.GetString() << endl;
    return 0;
}

5. 复制构造函数

调用函数时,如果函数的形参是对象,则传递的实参对象也被复制,因此需要复制构造函数。

5.1 浅复制存在的问题

浅复制是指在传递对象时,参数被复制过程中仅复制了指向内存的指针变量,而没有复制包含内容的内存块。


image.png

被浅复制的对象在某个函数中脱离作用域时就被析构,然而主调函数中往往还要对其进行一次析构,这就出现了问题。

5.2 使用复制构造函数确保深复制

声明构造函数的语法:

//拷贝构造函数的实现
class MyString
{
    Mystring const(const MyString& CopySource);
};
MyString::MyStrin(const MyString& CopySource)
{
    //拷贝构造函数实现代码
};

注意!

  1. 类包含原始指针成员时,必须要变细人复制构造函数和复制赋值运算符;
  2. 编写复制构造函数时,必须要将接受原对象的参数声明为const引用;
  3. 必须将类成员声明为std:string和智能指针类(而非原始指针),因为他们实现了复制构造函数,可以减少工作量;
  4. 除非万不得已,不要将类成员声明为原始指针;

5.3 C++11中的移动构造函数

如果遇到构造函数被连续调用两次的情况,实际上是不需要将一个对象反复构造两次的,此时应该使用移动复制构造函数
使用移动复制构造函数时,C++11编译器会自动使用它“移动”临时资源,避免深复制。

//拷贝构造函数被连续调用两次的情况
class MyString
{
    //...
};
MyString Copy(MyString& Source)    //复制string对象的函数
{
    MyString CopyForReturn(Source.GetString);
    return CopyForReturn;
}
int main()
{
    MyString sayHello("hello!");
    MyString sayHelloAgain(Copy(sayHello)); //执行两次拷贝构造函数,一次copy返回,一次是作为参数传入
    return 0;
}

//移动复制构造函数的声明
MyString(MyString&& MoveSource)
{
    if(MoveSource.Buffer != NULL)
    {
        Buffer = MoveSource.Buffer;
        MoveSource.Buffer = NULL;
    }
}

6. 构造函数和析构函数的其他用途

6.1 不允许复制的类(单例模式)

用private修饰所有的构造函数(默认构造函数,复制构造函数...)
用static修饰一个函数,这个函数中实例化了一个对象。从而使得该类中仅存在这一个对象。

注意:

  1. 关键字static用于累的数据成员时,该数据成员将在所有的实例中共享
  2. 将static用于函数中声明的局部变量时,该变量的值将在两次调用之间保持不变
  3. 将static用于成员函数时,该方法将在所有实例之间共享

6.3禁止在栈中实例化的类

如果要编写一个数据库类,且内部结构超过1GB,那么就应该禁止在栈上实例化该类,栈的空间没有那么大。
关键在于两点:

  1. 在栈上实例化对象时,将类的析构函数用private进行修饰,从而使得每次对象脱离作用域时,无法调用析构函数,编译器会检查该错误;而当在堆上实例化对象时,这种方法编译器不会报错,而导致内存泄露。
  2. 需要提供一个用来销毁实例的静态公有函数,只有这个类成员函数才能够调用析构函数。

7. this指针

  1. 在类中,this指针包含当前对象的地址
  2. 静态成员方法不会传递this指针,因为静态方法与实例不关联,由所有实例共享

8. sizeof()用于类

sizeof()是一个运算符,用于确定指定类型需要多少内存。
类实例化为对象后所占的内存实际上是在设计类时就确定了,与结构体类似,也有内存对齐的要求。
类对象所占的内存由类的数据成员决定。如int, bool, char* 等。

9. 结构体和类的区别

除非指定,类的成员默认为私有的,结构体的成员默认为公有的。

10. 声明友元

声明友元函数和友元类时,则友元类和友元函数可以从类外访问类的私有数据成员和方法。
友元函数的声明

//友元函数的声明
private:
    //...
    friend void function();
    //...
//友元类的声明
//...
private:
    friend class Human;
//...
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容