• c++面向对象


    一、类和对象

    类将具有共性的数据和方法封装在一起,加以权限区分,用户只能通过公共方法 访问私有数据。
    类的权限分为:private(私有)、protected(保护)、public(公有)3种权限。
    在类的外部,只有 public 修饰的成员才能被访问,在没有涉及继承与派生时, private protected 是同
    等级的,外部不允许访问。用户在类的外部可以通过 public 的方法间接访问 private protected 数据

    1.2构造函数

    1.2.1 初始化和清理
    当我们创建对象的时候 , 这个对象应该有一个初始状态,当对象销毁之前应该销毁自己创建的一些数据。 对象的初始化和清理也是两个非常重要的安全问题,一个对象或者变量没有初始时,对其使用后果是未 知,同样的使用完一个变量,没有及时清理,也会造成一定的安全问题。c++ 为了给我们提供这种问题 的解决方案,构造函数和析构函数,这两个函数将会被编译器自动调用,完成对象初始化和对象清理工 作。 无论你是否喜欢,对象的初始化和清理工作是编译器强制我们要做的事情,即使你不提供初始化操 作和清理操作,编译器也会给你增加默认的操作,只是这个默认初始化操作不会做任何事,所以编写类就应该顺便提供初始化函数。
    1.2.2 构造函数的概述
    类实例化对象的时候 系统自动调用构造函数 完成对象的初始化。
    如果用户不提供构造函数 编译器 会自动添加一个默认的构造函数(空函数)
    1.2.3 构造函数的定义方式
    构造函数名 和 类名相同,没有返回值类型(连 void 都不可以),可以有参数(可以重载),权限为
    public

    先给对象开辟空间(实例化) 然后调用构造函数(初始化)

    class Data
    {
    public :
    int mA ;
    public :
    // 无参构造函数
    Data ()
    {
    mA = 0 ;
    cout << " 无参构造函数 " << endl ;
    }
    // 有参构造函数
    Data ( int a )
    {
    mA = a ;
    cout << " 有参构造函数 mA=" << mA << endl ;
    }
    };
    int main ()
    {
    // 隐式调用无参构造函数(推荐)
    Data ob1 ;
    // 显示调用无参构造函数
    Data ob2 = Data ();
    // 隐式调用有参构造函数(推荐)
    Data ob3 ( 10 );
    // 显示调用有参构造函数
    Data ob4 = Data ( 10 );
    // 匿名对象 ( 无参 ) 当前语句技术 立即释放
    Data ();
    Data ( 20 );
    // 构造函数隐式转换(类中只有一个数据成员)
    Data ob5 = 100 ;
    }

    1.2.4 提供构造函数的影响
    如果用户不提供任何构造函数 编译器默认提供一个空的无参构造。
    如果用户定义了构造函数(不管是有参、无参),编译器不再提供默认构造函数。

    1.3 析构函数

    1.3.1 析构函数的定义方式
    函数名和类名称相同,在函数名前加 ~ ,没有返回值类型,没有函数形参。(不能被重载)
    当对象生命周期结束的时候 系统自动调用析构函数。
    先调用析构函数 再释放对象的空间。
    class Data1
    {
    public :
    int mA ;
    public :
    // 无参构造函数
    Data1 ()
    {
    mA = 0 ;
    cout << " 无参构造函数 " << endl ;
    }
    // 有参构造函数
    Data1 ( int a )
    {
    mA = a ;
    cout << " 有参构造函数 mA=" << mA << endl ;
    }
    // 析构函数
    ~Data1 ()
    {
    cout << " 析构函数 mA=" << mA << endl ;

      }
    };
    一般情况下,系统默认的析构函数就足够。但是如果一个类有 指针成员 ,这个类必须 写析构函数,释放 指针成员所指向空间。
    #include
    class Data2
    {
    public :
    char * name ;
    public :
    Data2 (){
    name = NULL ;
    }
    Data2 ( char * str )
    {
    name = new char [ strlen ( str ) + 1 ];
    strcpy ( name , str );
    cout << " 有参构造 " << endl ;
    }
    ~Data2 ()
    {
    if ( name != NULL )
    delete [] name ;
    cout << " 析构函数 " << endl ;
    }
    };
    int main ( int argc , char * argv [])
    {
    Data2 ob ( "hello world" );
    cout << ob . name << endl ;
    return 0 ;
    }
    也就是说析构掉的是堆区的hello world,对象的结束只会回收对象所占的内存空间,堆区空间不是对象所占的空间!

    1.4 拷贝构造函数

    1.4.1 拷贝构造函数的定义
    拷贝构造:本质是构造函数
    拷贝构造的调用时机:旧对象 初始化 新对象 才会调用拷贝构造。
    #include
    using namespace std ;
    class Data
    {
    public :
    int mA ;
    public :
    Data ()
    {
    cout << " 无参构造 " << endl ;
    }
    Data ( int a )
    {
    mA = a ;
    cout << " 有参构造 mA=" << mA << endl ;
    }
    #if 1
    // 拷贝构造的定义形式 :ob 就是旧对象的引用
    Data ( const Data & ob )
    {
    // 一旦实现了 拷贝构造函数 必须完成赋值操作
    mA = ob . mA ;
    cout << " 拷贝构造函数 " << endl ;
    }
    #endif
    ~Data ()
    {
    cout << " 析构函数 mA=" << mA << endl ;
    }
    };
    int main ( int argc , char * argv [])
    {
    Data ob1 ( 10 );
    // 旧对象给新对象初始化 就会调用拷贝构造函数
    Data ob2 = ob1 ;
    cout << "ob2.mA =" << ob2 . mA << endl ;
    return 0 ;
    }
    如果用户不提供拷贝构造 编译器会自动提供一个默认的拷贝构造( 完成赋值动作--浅拷贝
    1.4.2 拷贝构造 和 无参构造 有参构造的关系
    如果用户定义了 拷贝构造或者有参构造 都会屏蔽无参构造。
    如果用户定义了 无参构造或者有参构造 不会屏蔽拷贝构造。
    1.4.3 拷贝构造几种调用形式
    1、旧对象给新对象初始化 调用拷贝构造
    Data ob1 ( 10 );
    Data ob2 = ob1 ; // 调用拷贝构造
    2、给对象取别名 不会调用拷贝构造
    Data ob1 ( 10 );
    Data & ob2 = ob1 ; // 不会调用拷贝构造
    3、普通对象作为函数参数 调用函数时 会发生拷贝构造
    void func ( Data ob ) //Data ob=ob1
    {
    }
    int main ()
    {
    Data ob1 ( 100 ); // 有参构造
    func ( ob1 ); // 拷贝构造
    }
    4、函数返回值普通对象
    Visual Studio 会发生拷贝构造
    Qtcreater,linux不会发生

    1.4.4 拷贝构造的浅拷贝和深拷贝

    默认的拷贝构造 都是浅拷贝。
    如果类中没有指针成员, 不用实现拷贝构造和析构函数。
    如果类中有指针成员,且指向堆区空间, 必须实现析构函数释放指针成员指向的堆区空间,必须实现拷贝构造完成深拷贝动作
    #include
    #include
    using namespace std ;
    class Data5
    {
    public :
    char* name ;
    public :
    Data5 ()
    {
    name = NULL ;
    }
    Data5 ( char* str )
    {
    name = new char [ strlen ( str ) + 1 ];
    strcpy ( name , str );
    cout << " 有参构造 name=" << name << endl ;
    }
    Data5 ( const Data5 & ob ) // 深拷贝
    {
    // 为对象的指针成员申请独立的空间
    name = new char [ strlen ( ob . name ) + 1 ];
    strcpy ( name , ob . name );
    cout << " 拷贝构造函数 " << endl ;
    }
    ~Data5 ()
    {
    cout << " 析构函数 name = " << name << endl ;
    if ( name != NULL )
    {
    delete [] name ;
    name = NULL ;
    }
    }
    };
    void test05 ()
    {
    Data5 ob1 (( char * ) "hello world\n" );
    Data5 ob2 = ob1 ;
    }
    Data5 ( const Data5 & ob ) // 深拷贝
    {
    // 为对象的指针成员申请独立的空间
    name = new char [ strlen ( ob . name ) + 1 ];
    strcpy ( name , ob . name );
    cout << " 拷贝构造函数 " << endl ;
    }
    现在假设我们 Data ob2 = ob1 ; // 调用拷贝构造;如果我们没有这个深拷贝构造的话,就会用系统默认的拷贝,就是把ob1的空间的值赋给ob2.

    于是解决这个问题,就需要深拷贝构造。

    Data5 ( const Data5 & ob ) // 深拷贝
    {
    // 为对象的指针成员申请独立的空间
    name = new char [ strlen ( ob . name ) + 1 ];
    strcpy ( name , ob . name );
    cout << " 拷贝构造函数 " << endl ;
    }
    这个代码就是重新申请了空间,然后把ob的内容拷贝到新的空间,也就是他们各自指向各自的空间。所以ob1结束的时候先释放堆区在释放自己,ob2结束的时候释放自己的堆区在释放自己。
    深拷贝就是让指针成员拥有独立的指向空间,不让众多对象的指针成员指向同一个空间。

    1.5 初始化列表

    1.5.1 对象成员

    在类中定义的数据成员一般都是基本的数据类型。但是类中的成员也可以是对象,叫做对象成员。
    先调用对象成员的构造函数,再调用本身的构造函数。 析构函数和构造函数调用顺序相反,先构造,后析构。
    类会自动调用对象成员的 无参构造

    1.5.2 初始化列表

    类想调用对象成员 有参构造 必须使用 初始化列表
    1. #include
    2. using namespace std;
    3. class A
    4. {
    5. public:
    6. int mA;
    7. public:
    8. A()
    9. {
    10. mA = 0;
    11. cout << "A的无参构造" << endl;
    12. }
    13. A(int a)
    14. {
    15. mA = a;
    16. cout << "A的有参构造" << endl;
    17. }
    18. ~A()
    19. {
    20. cout << "A的析构函数" << endl;
    21. }
    22. };
    23. class B
    24. {
    25. public:
    26. int mB;
    27. A ob;//成员对象
    28. public:
    29. B()
    30. {
    31. cout << "B类的无参构造" << endl;
    32. }
    33. //初始化列表 成员对象 必须使用对象名+() 重要
    34. B(int a, int b) : ob(a)
    35. {
    36. mB = b;
    37. cout << "B类的有参构造" << endl;
    38. }
    39. ~B()
    40. {
    41. cout << "B的析构函数" << endl;
    42. }
    43. };
    44. int main(int argc, char* argv[])
    45. {
    46. B ob1(10, 20);
    47. cout << "mA =" << ob1.ob.mA << ", mB =" << ob1.mB << endl;
    48. return 0;
    49. }

    而如果这样写,就会调用A的无参构造

    1.6 explicit关键字

    c++ 提供了关键字 explicit ,禁止通过构造函数进行的隐式转换。声明为 explicit 的构造函数不能在隐式转 换中使用。 注意explicit 用于修饰构造函数 , 防止隐式转化。 是针对单参数的构造函数 ( 或者除了第一个参数外其余参 数都有默认值的多参构造) 而言。
    1. #include
    2. using namespace std;
    3. class MyString {
    4. public:
    5. explicit MyString(int n) {
    6. cout << "MyString(int n)!" << endl;
    7. }
    8. MyString(const char* str) {
    9. cout << "MyString(const char* str)" << endl;
    10. }
    11. };
    12. int main() {
    13. //给字符串赋值?还是初始化?
    14. //MyString str1 = 1;
    15. MyString str2(10);
    16. //寓意非常明确,给字符串赋值
    17. MyString str3 = "abcd";
    18. MyString str4("abcd");
    19. return 0;
    20. }

    MyString str1 = 1;  隐式转换,看的是后面1的类型!寻找1的类型为整型,就会去找MyString(int n)来执行,但是会发现这种书写容易让人造成歧义,因此加上explicit MyString(int n)这种写法就禁止让程序员出现MyString str1 = 1;这种写法。

    而下面的MyString str3 = "abcd";寓意比较明确就没加!

    1.7 类的对象数组

    对象数组:本质是数组 数组的每个元素是对象。
    1. #include
    2. using namespace std;
    3. class A
    4. {
    5. public:
    6. int mA;
    7. public:
    8. A()
    9. {
    10. mA = 0;
    11. cout << "A的无参构造 mA=" << mA << endl;
    12. }
    13. A(int a)
    14. {
    15. mA = a;
    16. cout << "A的有参构造mA=" << mA << endl;
    17. }
    18. ~A()
    19. {
    20. cout << "A的析构函数 mA = " << mA << endl;
    21. }
    22. };
    23. int main()
    24. {
    25. //对象数组 每个元素都会自动调用构造和析构函数
    26. //对象数组不初始化 每个元素 调用无参构造
    27. A arr1[5];
    28. //对象数组的初始化 必须显示使用有参构造 逐个元素初始化
    29. A arr2[5] = { A(10),A(20),A(30),A(40),A(50) };
    30. int n = sizeof(arr2) / sizeof(arr2[0]);
    31. int i = 0;
    32. for (i = 0; i < n; i++)
    33. {
    34. cout << arr2[i].mA << " ";
    35. }
    36. cout << endl;
    37. }

    这里析构会先析构  A arr2[5] = { A(10),A(20),A(30),A(40),A(50) };,其次才是   A arr1[5];,因为两个数组是同级别的,栈先进后出,然后   A arr2[5] = { A(10),A(20),A(30),A(40),A(50) };这里面的元素也是同级别的,于是显示先进后出!

    1.8 动态对象创建

    1.8.1 动态创建的概述

    当我们创建数组的时候,总是需要提前预定数组的长度,然后编译器分配预定长度的数组空间,在使用 数组的时,会有这样的问题,数组也许空间太大了,浪费空间,也许空间不足,所以对于数组来讲,如 果能根据需要来分配空间大小再好不过。 所以动态的意思意味着不确定性。 为了解决这个普遍的编程问 题,在运行中可以创建和销毁对象是最基本的要求。当然c 早就提供了动态内存分配( dynamic memory allocation) , 函数 malloc free 可以在运行时从堆中分配存储单元。 然而这些函数在 c++ 中不 能很好的运行,因为它不能帮我们完成对象的初始化工作。

    1.8.2 c语言的方式创建动态对象

    当创建一个 c++ 对象时会发生两件事 :
    1. 为对象分配内存
    2. 调用构造函数来初始化那块内存 第一步我们能保证实现,需要我们确保第二步一定能发生。 c++ 强迫我们这么做是因为使用未初始化的对象是程序出错的一个重要原因。 C 动态分配内存方法为了在运行时动态分配内存,c 在他的标准库中提供了一些函数 ,malloc 以及它的变种 calloc realloc, 释放内存的free, 这些函数是有效的、但是原始的,需要程序员理解和小心使用。为了使用 c 的动态内存分配函数在堆上创建一个类的实例,我们必须这样做:
    1. #define _CRT_SECURE_NO_WARNINGS
    2. #include
    3. using namespace std;
    4. class Person {
    5. public:
    6. Person() {
    7. mAge = 20;
    8. pName = (char*)malloc(strlen("john") + 1);
    9. strcpy(pName, "john");
    10. }
    11. void Init() {
    12. mAge = 20;
    13. pName = (char*)malloc(strlen("john") + 1);
    14. strcpy(pName, "john");
    15. }
    16. void Clean() {
    17. if (pName != NULL) {
    18. free(pName);
    19. }
    20. }
    21. public:
    22. int mAge;
    23. char* pName;
    24. };
    25. int main() {
    26. //分配内存
    27. Person* person = (Person*)malloc(sizeof(Person));
    28. if (person == NULL) {
    29. return 0;
    30. }
    31. //调用初始化函数
    32. person->Init();
    33. //清理对象
    34. person->Clean();
    35. //释放person对象
    36. free(person);
    37. return 0;
    38. }
    在c++看来c存在的问题:
    1) 程序员必须确定对象的长度。 (sizeof(Person)
    2) malloc 返回一个 void 指针, c++ 不允许将 void赋值给其他任何指针,必须强转。 (Person*)
    3) malloc可能申请内存失败,所以必须判断返回值来确保内存分配成功。  
     if (person == NULL) {
            return 0;
        }
    4) 用户在使用对象之前必须记住对他初始化,构造函数不能显示调用初始化 ( 构造函数是由编译器调用 ) ,用 户有可能忘记调用初始化函数。
    //调用初始化函数
    person->Init();
    //清理对象
    person->Clean();
    //释放person对象
    free(person);
    c 的动态内存分配函数太复杂,容易令人混淆,是不可接受的, c++中我们推荐使用运算符new 和
    delete。

    1.8.3 new创建动态对象

    C++ 中解决动态内存分配的方案是把创建一个对象所需要的操作都结合在一个称为 new 的运算符里。当 用new 创建一个对象时, 它就在堆里为对象分配内存并调用构造函数完成初始化。
    Person* person = new Person;
    New 操作符能确定在调用构造函数初始化之前内存分配是成功的,所有不用显式确定调用是否成功。现在我们发现在堆里创建对象的过程变得简单了,只需要一个简单的表达式,它带有内置的长度计算、类型转换和安全检查。这样在堆创建一个对象和在栈里创建对象一样简单

    1.8.4 delete释放动态对象

    new 表达式的反面是 delete 表达式。 delete表达式先调用析构函数,然后释放内存。而c中的free不会调用析构函数,直接会释放内存!
    1. #define _CRT_SECURE_NO_WARNINGS
    2. #include
    3. using namespace std;
    4. class Person {
    5. public:
    6. Person() {
    7. cout << "无参构造函数!" << endl;
    8. pName = new char[strlen("undefined") + 1];
    9. strcpy(pName, "undefined");
    10. mAge = 0;
    11. }
    12. Person(char* name, int age) {
    13. cout << "有参构造函数!" << endl;
    14. pName = new char[strlen(name) + 1];
    15. strcpy(pName, name);
    16. mAge = age;
    17. }
    18. void ShowPerson() {
    19. cout << "Name:" << pName << " Age:" << mAge << endl;
    20. }
    21. ~Person() {
    22. cout << "析构函数!" << endl;
    23. if (pName != NULL) {
    24. delete[] pName;
    25. pName = NULL;
    26. }
    27. }
    28. public:
    29. char* pName;
    30. int mAge;
    31. };
    32. int main() {
    33. Person* person1 = new Person;//会调用无参构造
    34. Person* person2 = new Person("John", 33);//会调用有参构造
    35. person1->ShowPerson();
    36. person2->ShowPerson();
    37. delete person1;
    38. delete person2;
    39. }

    1.8.5 动态对象数组

    当创建一个对象数组的时候,必须对数组中的每一个对象调用构造函数,除了在栈上可以聚合初始化, 必须提供一个默认的构造函数!
    1. class Person {
    2. public:
    3. Person() {
    4. pName = NULL;
    5. mAge = 0;
    6. }
    7. Person(char* name, int age) {
    8. pName = new char[strlen(name) + 1];
    9. strcpy(pName, name);
    10. mAge = age;
    11. }
    12. ~Person() {
    13. if (pName != NULL) {
    14. delete[] pName;
    15. }
    16. }
    17. public:
    18. char* pName;
    19. int mAge;
    20. };
    21. void test() {
    22. //栈聚合初始化
    23. Person person[] = { Person("john", 20), Person("Smith", 22) };
    24. cout << person[1].pName << endl;
    25. //创建堆上对象数组必须提供构造函数
    26. Person* workers = new Person[20];
    27. delete[] workers;
    28. }

    Person* workers = new Person[20];
    delete[] workers;

    因为new的时候有[],所以delete也要有!

    1.9 静态成员

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

    1.9.1 静态成员变量

    static 修饰的静态成员 属于类而不是对象。 (所有对象共享 一份 静态成员数据)
    static修饰的成员 定义类的时候 必须分配空间。
    static修饰的静态成员数据 必须类中定义 类外初始化。
    1. class Data
    2. {
    3. public:
    4. int a;//普通成员数据
    5. //类中定义
    6. static int b;//静态成员数据
    7. };
    8. //类外初始化
    9. int Data::b = 100;//不用加static
    10. void test01()
    11. {
    12. //静态成员数据 通过类名称直接访问(属于类)
    13. cout << Data::b << endl;
    14. //静态成员数据 通过对象访问(共享)
    15. Data ob1;
    16. cout << ob1.b << endl;//100
    17. ob1.b = 200;
    18. Data ob2;
    19. ob2.b = 300;
    20. cout << Data::b << endl;//300
    21. }
    案例 1 :使用静态成员数据 统计对象的个数
    1. class Data2
    2. {
    3. public:
    4. int mA;
    5. static int count;
    6. public:
    7. Data2()
    8. {
    9. count++;
    10. }
    11. Data2(int a)
    12. {
    13. mA = a;
    14. count++;
    15. }
    16. Data2(const Data2& ob)
    17. {
    18. count++;
    19. }
    20. ~Data2()
    21. {
    22. count--;
    23. }
    24. };
    25. int Data2::count = 0;
    26. void test02()
    27. {
    28. Data2 ob1;
    29. Data2 ob2(10);
    30. Data2 ob3 = ob2;
    31. cout << "对象个数:" << Data2::count << endl;//3
    32. {
    33. Data2 ob4;
    34. Data2 ob5;
    35. cout << "对象个数:" << Data2::count << endl;//5
    36. }
    37. cout << "对象个数:" << Data2::count << endl;//3
    38. }

    1.9.2 静态成员函数

    静态成员函数 是属于类 而不是对象(所有对象 共享)
    1. class Data
    2. {
    3. static void func()//静态成员函数
    4. {
    5. }
    6. }

    说白了 static  静态成员函数就是为了能够没有进行对象调用也可以获取到私有的变量值!

    静态成员函数 可以直接通过类名称访问

    静态成员函数内 只能操作静态成员数据。

    1.9.3 单例模式设计

    单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模 式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案
    1. #define _CRT_SECURE_NO_WARNINGS
    2. #include
    3. using namespace std;
    4. #include
    5. using namespace std;
    6. class SingleTon//单例模式
    7. {
    8. //构造私有化 防止实例化其他对象
    9. private:
    10. SingleTon() {
    11. count = 0;
    12. cout << "构造" << endl;
    13. }
    14. SingleTon(const SingleTon & ob) {
    15. count = 0;
    16. }
    17. ~SingleTon()
    18. {
    19. cout << "析够" << endl;
    20. }
    21. private:
    22. //const防止p 在类内部 被修改指向
    23. static SingleTon* const p;//保存唯一的实例地址
    24. int count;//统计任务执行次数
    25. public:
    26. static SingleTon* getSingleTon(void)//获取唯一的实例地址
    27. {
    28. return p;
    29. }
    30. //用户自定义 任务函数
    31. void printString(char* str)
    32. {
    33. count++;
    34. cout << "当前第" << count << "次任务打印:" << str << endl;
    35. }
    36. };
    37. SingleTon* const SingleTon::p = new SingleTon;//创建唯一的实例
    38. int main(int argc, char* argv[])
    39. {
    40. //获取单例的地址
    41. SingleTon* p1 = SingleTon::getSingleTon();
    42. p1->printString("离职证明1");
    43. p1->printString("学历证明1");
    44. p1->printString("学位证明1");
    45. p1->printString("身份证明1");
    46. SingleTon* p2 = SingleTon::getSingleTon();
    47. p2->printString("离职证明2");
    48. p2->printString("学历证明2");
    49. p2->printString("学位证明2");
    50. p2->printString("身份证明2");
    51. return 0;
    52. }

    1.10 c++面向对象模型

    1.10.1 成员变量和函数的存储

    c++ 实现了 封装 数据 处理数据的操作 ( 函数 )” 是分开存储的。 c++ 中的非静态数据成员直接内含在 类对象中,成员函数虽然内含在class 声明之内,却不出现在对象中。 每一个非内联成员函数只会诞生一 份函数实例。
    sizeof(Data1)的大小只是 a b所占空间大小(类的对象所占空间大小)。
    1. #define _CRT_SECURE_NO_WARNINGS
    2. #include
    3. using namespace std;
    4. class MyClass01 {
    5. public:
    6. int mA;
    7. };
    8. class MyClass02 {
    9. public:
    10. int mA;
    11. static int mB;
    12. };
    13. class MyClass03 {
    14. public:void printMyClass() {
    15. cout << "hello world!" << endl;
    16. }
    17. public:
    18. int mA;
    19. static int mB;
    20. };
    21. class MyClass04 {
    22. public:
    23. void printMyClass() {
    24. cout << "hello world!" << endl;
    25. }
    26. static void ShowMyClass() {
    27. cout << "hello world!" << endl;
    28. }
    29. public:
    30. int mA;
    31. static int mB;
    32. };
    33. int main() {
    34. MyClass01 mclass01;
    35. MyClass02 mclass02;
    36. MyClass03 mclass03;
    37. MyClass04 mclass04;
    38. cout << "MyClass01:" << sizeof(mclass01) << endl; //4
    39. //静态数据成员并不保存在类对象中
    40. cout << "MyClass02:" << sizeof(mclass02) << endl; //4
    41. //非静态成员函数不保存在类对象中
    42. cout << "MyClass03:" << sizeof(mclass03) << endl; //4
    43. //静态成员函数也不保存在类对象中
    44. cout << "MyClass04:" << sizeof(mclass04) << endl; //4
    45. return 0;
    46. }
    通过上面的案例,我们可以的得出: C++ 类对象中的变量和函数是分开存储。

    1.10.2 this指针

    1this指针工作原理
    通过上例我们知道, c++ 的数据和操作也是分开存储,并且每一个非内联成员函数只会诞生一份函数实 例,也就是说多个同类型的对象会共用一块代码 那么问题是:这一块代码是如何区分那个对象调用自己的呢?
    c++ 通过提供特殊的对象指针, this 指针,解决上述问题。 this 指针指向被调用的成员函数所属的对象。
    成员函数通过 this 指针即可知道操作的是那个对象的数据。 This 指针是一种隐含指针,它隐含于每个类的
    非静态成员函数中。 This 指针无需定义,直接使用即可。
    注意:静态成员函数内部没有 this 指针,静态成员函数不能操作非静态成员变量
    2、函数形参和成员同名可以使用this指针解决。
    3、this来完成链式操作

    1.10.3 const修饰成员函数

    const 修饰的成员函数时, const 修饰 this 指针指向的内存区域,成员函数体内不可以修改本类中的任何普通成员变量, 当成员变量类型符前用mutable 修饰时例外
    1. #define _CRT_SECURE_NO_WARNINGS
    2. #include
    3. using namespace std;
    4. class Data
    5. {
    6. public:
    7. int a;
    8. int b;
    9. mutable int c;
    10. public:
    11. Data(int a, int b, int c)
    12. {
    13. this->a = a;
    14. this->b = b;
    15. this->c = c;
    16. }
    17. //const 修饰成员函数为只读(该成员函数不允许对 成员数据 赋值) mutable修饰的成员除外
    18. void showData(void) const
    19. {
    20. //a = 100;//err
    21. c = 100;
    22. cout << a << " " << b << " " << c << endl;
    23. }
    24. };
    25. int main()
    26. {
    27. Data ob1(10, 20, 30);
    28. ob1.showData();
    29. }

    1.11 友元

    类的主要特点之一是数据隐藏,即类的私有成员无法在类的外部 ( 作用域之外 ) 访问。但是,有时候需要在类的外部访问类的私有成员,怎么办?
    解决方法是使用友元函数,友元函数是一种特权函数, c++ 允许这个特权函数访问私有成员。这一点从 现实生活中也可以很好的理解: 比如你的家,有客厅,有你的卧室,那么你的客厅是Public 的,所有来 的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去,但是呢,你也可以允许你的闺蜜好基友进去。 程序员可以把一个全局函数、某个类中的成员函数、甚至整个类声明为友元。

    1.11.1 友元的语法

    使用 friend 关键字声明友元。
    friend 关键字只出现在声明处,一个函数或者类 作为了另一个类的友元 那么这个函数或类 就可以直接 访问 另一个类的私有数据。
    友元 重要用在运算符重载上。

    1.11.2 普通全局函数作为类的友元

    1. #define _CRT_SECURE_NO_WARNINGS
    2. #include
    3. using namespace std;
    4. class Room
    5. {
    6. friend void visiting01(Room& room);
    7. private:
    8. string bedRoom;//卧室
    9. public:
    10. string setingRoom;//客厅
    11. public:
    12. Room(string bedRoom, string setingRoom)
    13. {
    14. this->bedRoom = bedRoom;
    15. this->setingRoom = setingRoom;
    16. }
    17. };
    18. //普通全局函数
    19. void visiting01(Room& room)
    20. {
    21. cout << "访问了" << room.setingRoom << endl;
    22. cout << "访问了" << room.bedRoom << endl;
    23. }
    24. int main(int argc, char* argv[])
    25. {
    26. Room room("卧室", "客厅");
    27. visiting01(room);
    28. return 0;
    29. }

    1.11.3 类的某个成员函数 作为另一个类的友元

    1. #define _CRT_SECURE_NO_WARNINGS
    2. #include
    3. using namespace std;
    4. class Room;//向前声明 只能说明类名称
    5. class goodGay
    6. {
    7. public:
    8. void visiting01(Room& room);
    9. void visiting02(Room& room);
    10. }; class Room
    11. {
    12. friend void goodGay::visiting02(Room& room);
    13. private:
    14. string bedRoom;//卧室
    15. public:
    16. string setingRoom;//客厅
    17. public:
    18. Room(string bedRoom, string setingRoom)
    19. {
    20. this->bedRoom = bedRoom;
    21. this->setingRoom = setingRoom;
    22. }
    23. };
    24. void goodGay::visiting01(Room& room)
    25. {
    26. cout << "访问了" << room.setingRoom << endl;
    27. //cout<<"访问了"<
    28. }
    29. void goodGay::visiting02(Room& room)
    30. {
    31. cout << "好基友张三访问了" << room.setingRoom << endl;
    32. cout << "好基友张三访问了" << room.bedRoom << endl;
    33. }
    34. int main(int argc, char* argv[])
    35. {
    36. Room room("卧室", "客厅");
    37. goodGay ob;
    38. ob.visiting01(room);
    39. ob.visiting02(room);
    40. return 0;
    41. }

    1.11.4 整个类作为 另一个类的友元

    这个类的所有成员函数 都可以访问另一个类的私有数据 .
    1. #define _CRT_SECURE_NO_WARNINGS
    2. #include
    3. using namespace std;
    4. class Room;//向前声明 只能说明类名称
    5. class goodGay
    6. {
    7. public:
    8. void visiting01(Room& room);
    9. void visiting02(Room& room);
    10. };
    11. class Room
    12. {
    13. friend class goodGay;
    14. private:
    15. string bedRoom;//卧室
    16. public:
    17. string setingRoom;//客厅
    18. public:
    19. Room(string bedRoom, string setingRoom) {
    20. this->bedRoom = bedRoom;
    21. this->setingRoom = setingRoom;
    22. }
    23. };
    24. void goodGay::visiting01(Room& room)
    25. {
    26. cout << "访问了" << room.setingRoom << endl;
    27. cout << "访问了" << room.bedRoom << endl;
    28. }
    29. void goodGay::visiting02(Room& room)
    30. {
    31. cout << "好基友访问了" << room.setingRoom << endl;
    32. cout << "好基友访问了" << room.bedRoom << endl;
    33. }
    34. int main(int argc, char* argv[])
    35. {
    36. Room room("卧室", "客厅");
    37. goodGay ob;
    38. ob.visiting01(room);
    39. ob.visiting02(room);
    40. return 0;
    41. }

    1.11.5 友元的注意事项

    1 .友元关系不能被继承。
    2 .友元关系是单向的,类 A 是类 B 的朋友,但类 B 不一定是类 A 的朋友。
    3 .友元关系不具有传递性。类 B 是类 A 的朋友,类 C 是类 B 的朋友,但类 C 不一定是类 A 的朋友

    1.11.6 友元案例(遥控器的类)

    请编写电视机类,电视机有开机和关机状态,有音量,有频道,提供音量操作的方法,频道操作的方 法。由于电视机只能逐一调整频道,不能指定频道,增加遥控类,遥控类除了拥有电视机已有的功能, 再增加根据输入调台功能。
    提示:遥控器可作为电视机类的友元类
    1. #define _CRT_SECURE_NO_WARNINGS
    2. #include
    3. using namespace std;
    4. #include
    5. using namespace std;
    6. class TV;
    7. //遥控器的类作为TV的友元
    8. class Remote
    9. {
    10. private:
    11. TV* p;
    12. public:
    13. Remote(TV* p);
    14. void offOrOn(void);
    15. void upVolume(void);
    16. void downVolume(void);
    17. void upChannel(void); void downChannel(void);
    18. void showTv(void);
    19. void setChannel(int channel);
    20. };
    21. class TV
    22. {
    23. friend class Remote;
    24. enum { OFF, ON };
    25. enum { minVol, maxVol = 10 };
    26. enum { minChan, maxChan = 25 };
    27. private:
    28. int state;
    29. int volume;
    30. int channel;
    31. public:
    32. TV()
    33. {
    34. state = OFF;
    35. volume = minVol;
    36. channel = minChan;
    37. }
    38. void offOrOn(void);
    39. void upVolume(void);
    40. void downVolume(void);
    41. void upChannel(void);
    42. void downChannel(void);
    43. void showTv(void);
    44. };
    45. int main(int argc, char* argv[])
    46. {
    47. TV tv;
    48. Remote re(&tv);
    49. re.offOrOn();
    50. re.upVolume();
    51. re.upVolume();
    52. re.setChannel(10);
    53. re.showTv();
    54. return 0;
    55. }
    56. void TV::offOrOn()
    57. {
    58. state = (state == OFF ? ON : OFF);
    59. }
    60. void TV::upVolume()
    61. {
    62. if (volume == maxVol)
    63. {
    64. cout << "音量已经最大" << endl;
    65. return;
    66. }volume++;
    67. }
    68. void TV::downVolume()
    69. {
    70. if (volume == minVol)
    71. {
    72. cout << "音量已经最小" << endl;
    73. return;
    74. }
    75. volume--;
    76. }
    77. void TV::upChannel()
    78. {
    79. if (channel == maxChan)
    80. {
    81. cout << "频道已经最大" << endl;
    82. return;
    83. }
    84. channel++;
    85. }
    86. void TV::downChannel()
    87. {
    88. if (channel == minChan)
    89. {
    90. cout << "频道已经最小" << endl;
    91. return;
    92. }
    93. channel--;
    94. }
    95. void TV::showTv()
    96. {
    97. cout << "当前电视机的状态:" << (state == OFF ? "关" : "开") << endl;
    98. cout << "当前电视机的音量:" << volume << endl;
    99. cout << "当前电视机的频道:" << channel << endl;
    100. }
    101. Remote::Remote(TV* p)
    102. {
    103. this->p = p;
    104. }
    105. void Remote::offOrOn()
    106. {
    107. p->offOrOn();
    108. }
    109. void Remote::upVolume()
    110. {
    111. p->upVolume();
    112. }
    113. void Remote::downVolume()
    114. {
    115. p->downVolume();
    116. }
    117. void Remote::upChannel()
    118. {
    119. p->upChannel();
    120. }
    121. void Remote::downChannel()
    122. {
    123. p->downChannel();
    124. }
    125. void Remote::showTv()
    126. {
    127. p->showTv();
    128. }
    129. void Remote::setChannel(int channel)
    130. {
    131. if (channel >= TV::minChan && channel <= TV::maxChan)
    132. {
    133. p->channel = channel;
    134. }
    135. else
    136. {
    137. cout << "频道" << channel << "不在有效范围内" << endl;
    138. }
    139. }

    1.11.7 设计动态数组类案例

    1.12 运算符重载

    1.12.1 运算符重载基本概念

    运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
    语法: 定义重载的运算符就像定义函数,只是该函数的名字是 operator@, 这里的 @ 代表了被重载的运算 符。
    思路:
    1 、弄懂运算符的运算对象的个数。(个数决定了 重载函数的参数个数)
    2 、识别运算符左边的运算对象 是类的对象 还是其他 .
    类的对象:全局函数实现(不推荐) 成员函数实现(推荐,少一个参数)
    其他:只能是全局函数实现

    1.12.2 重载<<运算符(全局函数实现)

    1. #define _CRT_SECURE_NO_WARNINGS
    2. #include
    3. using namespace std;
    4. #include
    5. using namespace std;
    6. class Person
    7. {
    8. friend ostream& operator<<(ostream& out, Person& ob);
    9. private:
    10. int num;
    11. string name;
    12. float score;
    13. public:
    14. Person() {}
    15. Person(int num, string name, float score) :num(num), name(name), score(score) {}
    16. };
    17. //全局函数重载operator <<
    18. ostream & operator<<(ostream & out, Person & ob)
    19. {
    20. out << ob.num << " " << ob.name << " " << ob.score << endl;
    21. return out;
    22. }
    23. int main(int argc, char* argv[])
    24. {
    25. Person lucy(100, "lucy", 99.8f);
    26. Person bob(101, "bob", 99.8f);
    27. cout << lucy << bob << endl;//operator<<(cout, lucy);
    28. return 0;
    29. }
    如果使用全局函数 重载运算符 必须将全局函数设置成友元。

    1.12.3 重载>>运算符(全局函数实现)

    1. #define _CRT_SECURE_NO_WARNINGS
    2. #include
    3. using namespace std;
    4. #include
    5. #include
    6. using namespace std;
    7. class Person
    8. {
    9. friend ostream& operator<<(ostream& out, Person& ob);
    10. friend istream& operator>>(istream& in, Person& ob); private:
    11. int num;
    12. string name;
    13. float score;
    14. public:
    15. Person() {}
    16. Person(int num, string name, float score) :num(num), name(name), score(score) {}
    17. };
    18. //全局函数重载operator<<
    19. ostream& operator<<(ostream& out, Person& ob)
    20. {
    21. out << ob.num << " " << ob.name << " " << ob.score << endl;
    22. return out;
    23. }
    24. //全局函数重载operator>>
    25. istream& operator>>(istream& in, Person& ob)
    26. {
    27. in >> ob.num >> ob.name >> ob.score;
    28. return in;
    29. }
    30. int main(int argc, char* argv[])
    31. {
    32. Person lucy;
    33. Person bob;
    34. cin >> lucy >> bob;
    35. cout << lucy << bob << endl;
    36. return 0;
    37. }

    1.12.4 可以重载的运算符

  • 相关阅读:
    浅谈嵌入式系统结构和嵌入式Linux
    【C语言】关键字const、static、extern、register、volatile、auto的作用
    前端培训技术Webpack优化构建速度
    高等数学660---从214到221
    同样做软件测试,为什么有人月入3k-5k,有人能拿到17-20k?
    CSS:上面固定高度、下面填充满,上面消失,下面仍然填充满
    运输层(计算机网络谢希仁第八版)——学习笔记五
    优先队列题目:数组的最小偏移量
    AlexNet——训练花数据集
    XXE外部实体注入漏洞总结
  • 原文地址:https://blog.csdn.net/m0_63654706/article/details/134457451