面向对象的程序设计

 面向对象的程序设计

    面向对象(Object Oriented, OO)是当代计算软件开发方法;

    对象:现实世界中的各种具体的或抽象的“事物”。

一 、类

    类是具有相同属性和行为的一组对象的集合,它为属于该类的全部对象提供了统一的抽象描述,其内部包括属性和行为两个主要部分

    1、语法格式:

        class 类名称

        {

          public://访问权限修饰符

            公有成员(外部可以调用)

          protected://访问权限修饰符

            保护成员(外部不可调用、派生类可以调用)

          private://访问权限修饰符

            私有成员(外部不可调用、派生类不可调用)

        };

    2、类对象

        类的对象是该类的某一特定实体,即类类型的变量。

        声明形式: 类名 对象名;

      3、类成员

        成员变量

          class 类名

          {

          访问权限:

               类型  变量名;  //声明的时候不占用内存

          };

        注意:在定义成员变量时,不对成员变量赋初始值。

      成员函数

      可以写在类体重也可以写在类体外;

      示例:

#pragma once
#include <iostream>

class Color
{
public:
    void setName(const char* n)
    {
        strcpy_s(name, n);
    }
private:
    char name[32];
}
成员函数写在类体中
 1 Color.h
 2 #pragma once
 3 #include <iostream>
 4 
 5 class Color
 6 {
 7 public:
 8     void setName(const char* n);
 9 private:
10     char name[32];
11 };
12 
13 Color.cpp
14 #include "stdafx.h"
15 #include "Color.h"
16 
17 
18 void Color::setName(const char* n)
19 {
20     strcpy_s(name, n);
21 }    
成员函数写在类体外

 

 

    4、类成员的访问

        (1)类中成员可以直接访问

            直接使用成员名。

        (2)类外访问

            对象使用“对象名.成员名”的方式访问public属性的成员。

             指针使用“指针变量->成员名”的方式访问public属性的成员。

#include "stdafx.h"
#include <iostream>
using namespace std;

class Car//定义car类
{
public:
    void start()//成员函数
    {
        cout << color << "" << name << "启动了" << endl;

    }
    void stop()//成员函数
    {
        cout << color << "" << name << "停下了" << endl;

    }
    void set(const char * n, const char * c)//成员函数
    {
        strcpy_s(name, n);
        strcpy_s(color, c);
    }
private:
    char name[32];//成员变量
    char color[32];//成员变量

};

int main()
{
    Car a;//定义一个car类的对象a
    a.set("宝马", "红色");
    a.start();
    a.stop();

    Car b;
    b.set("奔驰", "蓝色");
    Car *p = &b;
    p->start();
    p->stop();
    return 0;
}
示例

 

二、构造函数

  概念:构造函数是实现数据成员初始化的特殊成员函数

  特点:(1)与类同名,没有返回值;

     (2)创建对象时,构造函数被自动调用。每创建一个对象都必须调用一次构造函数,每调用一次构造函数必定创建一个对象。

   分类 : (1)无参构造函数:没有参数;

      (2)普通构造函数:普通参数;

      (3) 拷贝构造函数:参数为对象的引用。

  语法格式:

      (1)类中定义格式

          类名(形参列表)

          {…} //函数体,对数据成员赋值 类中声明,

      (2)类外定义

          类中声明 类名(形参列表);

          类外定义 类名::类名(形参列表)

              {…} //函数体

  一、缺省的构造函数

  1、缺省构造函数的种类

    (1)系统自动产生的构造函数 类名() {}
      Α、 用户未定义构造函数时,系统会自动产生构造函数,用户一旦定义,系统就不会再产生构造函数。

    (2) 用户定义的无参构造函数

class String
{
public:
    String()  //无参构造函数
     {
         memset(str, 0, sizeof(str));
         cout << "用户定义无参构造函数" << endl;
     }

    (3)用户定义的所有参数都有缺省值的构造函数。

     

 1 class String
 2 {
 3 public:
 4     String(char *s = nullptr)  //用户定义的所有参数都有缺省值的构造函数
 5     {
 6         cout << "所有参数都有缺省值" << endl;
 7         if (s == nullptr)
 8         {
 9             memset(str, 0, sizeof(str));
10         }
11         else
12         {
13             strcpy_s(str, s);
14         }
15     }

 

    2、缺省的构造函数的调用

      类名   类对象

        定义类对象时 有两件事发生:Α 、创建了类对象 。  Β、调用构造函数。

  二、有参的构造函数

    1、有参构造函数的定义

1 class String
2 {
3 public:
4     String(char *  p)  //有参构造函数
5      {
6          memset(str, 0, sizeof(str));
7          cout << "用户定义有参构造函数" << endl;
8      }
有参构造函数

    2、有参构造函数的调用

int main()
{
    String s("Hello world!");
    s.output();
    return 0;
}

  三、拷贝构造函数

    概念:拷贝构造函数是一种特殊的构造函数,其形参为本类的对象的引用

      作用:从一个已有对象,来初始化新创建的对象

    语法格式:

 

            class 类名

             {

              public: 类名() {…} //无参构造函数

              类名(形参列表) {…} //有参构造函数

              类名(类名& 对象名) {…} //拷贝构造函数 };

             // 调用拷贝构造函数的场景(假设有Car c1定义):

              Car c2 = c1;

              Car c3(c1);

class String
{
public:
    String(const char* s)
    {
        //memset(str, 0, sizeof(str));
        strcpy_s(str, s);
    }

    //拷贝构造函数
    String(const String& other) //一般都这么写other
    {
        strcpy_s(str, other.str);
    }

    void output()
    {
        cout << str << endl;
    }
private:
    char str[128];
};

int main()
{
    String a("123453333333333333333333333333333333333");
    String b(a);
    cout << "a=";
    a.output();

    cout << "b=";
    b.output();
    return 0;
}

  四、浅拷贝和深拷贝

    从已存在对象来创建新的对象,系统会调用拷贝构造函数,如果用户没有定义拷贝构造函数,则系统会自动生成默认的拷贝构造函数,进行值拷贝。如果用户定义了拷贝构造函数,系统会调用用户顶替拷贝构造函数,进行拷贝。

    (1)默认拷贝构造函数可以完成对象的数据成员简单的复制,实际上默认的拷贝构造函数只是将指针复制。

   (2)在对含有指针成员的对象进行拷贝时,必须要自己定义拷贝构造函数,使拷贝后的对象指针成员有自己的内存空间,即进行深拷贝,这样就避免了内存泄漏发生。

  浅拷贝:类成员变量中有指针,文件、句柄等情况下,浅拷贝就有可能让类无法正常工作。

    原因:以指针为例,拷贝只是复制了指针变量的地址,而其内存数据未拷贝,一旦s1释放内存,等到s2释放内存时,已找不到指针所指向的内存,此时为野指针,带来的后果具有不可预测性,程序执行到此会崩溃,

                         

  深拷贝:让新产生的对象,其成员变量对资源的引用操持独立,相互之间不受影响,仅保持“值”相同。

                 

 

  深拷贝示例

 

#include "stdafx.h"
#include <iostream>  
using namespace std;

class Student
{
private:
    int num;
    char *name;
public:
    Student();
    ~Student();
    Student(const Student &s);//拷贝构造函数,const防止对象被改变
};

Student::Student()
{
    name = new char(20);
    cout << "Student" << endl;

}
Student::~Student()
{
    cout << "~Student " << (int)name << endl;
    delete name;
    name = NULL;
}
Student::Student(const Student &s)
{
    name = new char(20);
    memcpy(name, s.name, strlen(s.name));
    cout << "copy Student" << endl;
}

int main()
{
    {
        Student s1;
        Student s2(s1);// 复制对象
    }

    return 0;
}
深拷贝

   总结: 浅拷贝会把指针变量的地址复制; 深拷贝会重新开辟内存空间

 

 

三、析构函数

  1、概念:用于撤销对象的成员函数造成的垃圾,比如开辟的空间(对象释放前系统自动调用)。、 

  2、语法格式

      (1)类中定义

          ~类名()

          {…} //函数体

      (2) 类中说明,类外定义

         Α  类中声明

          ~类名(); //类中声明

           Β   类外定义

          类名::~类名()

          {…} //函数体

  3、注意事项:

    (1)析构函数的名称由运算符“~”与类名组成;

    (2)析构函数无参数,无返回值;

    (3)析构函数不可以重载,即一个类只有一个析构函数;

    (4)如果用户没有定义,系统会自动生成默认析构函数: 类名::~类名() {}

    (5)当类中用new运算符分配了动态空间时,必须定义析构函数,并在函数体中用delete运算符释放动态存储空间。

    (6)析构函数调用与构造函数相反,先构造的后析构,后构造的先析构;

  4、示例

#include "stdafx.h"
#include <iostream>
#include <string.h>
using namespace std;
class String
{
public:
    String(const char *str)
    {
        int len = strlen(str) + 1;
        p = new char[len];
        strcpy_s(p, len, str);
        cout << "创建对象" << endl;
    }

    void output()
    {
        cout << p << endl;
        cout << "打印对象" << endl;
    }

    ~String()
    {
        if (p != nullptr)
        {
            delete [ ] p;
            p = nullptr;
            cout << "对象析构" << endl;
        }
    }

private:
    char *p;
};

int main()
{
    String s("hello world");
    s.output();
    return 0;
析构函数字符串练习

四、成员变量初始化

  1、类成员变量初始有两种方式

    方式一: 在构造函数体中对成员变量进行赋值

    语法: 类名(形参列表)

    {

      成员变量1 = 表达式;

      成员变量2 = 表达式;

      …

      成员变量n = 表达式;

    }

    方式二:使用初始化列表

      语法: 类名(形参列表) : 对象名1(实参列表) , 对象名1(实参列表) , … ,对象名n(实参列表)

      {

        … //函数体

      }

  2、成员变量初始化的顺序

    (1)成员变量的初始化顺序和成员变量声明顺序相关,先声明,先初始化

      

    (2)先调用对象成员所属类的构造函数,再执行自身类的函数体

     

五、成员变量和成员变量函数

1、静态成员

  分类:静态成员变量和静态成员函数

   静态成员变量

    1、格式语法

          class 类名 {

                 访问权限:

                    static 类型 变量名;

                 };

    2、说明:

 

        (1)静态成员变量不属于某个对象,而是属于类,它由某个类的所有对象共有。

        (2)静态成员变量的定义 静态成员变量定义后,必须对它进行初始化,并且一定要在类外进行。

        (3)静态成员变量只能够初始化一次

 

        (4)初始化的形式如下: 类型 类名::静态成员变量 = 表达式; 初始化时不加关键字static

 

    3、示例:

 

#include "stdafx.h"
#include <iostream>
using namespace std;

class Point
{
    int x;
    int y;

public:
    static int no;//声明静态成员变量
    Point(int a, int b) :x(a), y(b)
    {
        ++no;
    }
};

int  Point::no = 0;//必须在类体外初始化静态成员变量

void  main()
{
    Point p(1,10);
    Point p1(2,4);
    cout << Point::no << endl;//调用静态成员变量
}
静态成员变量

 

 

 

  静态成员函数

 

    语法格式:

         class 类名 {

           访问权限:

              static 返回值 函数名(形参列表);

              }

 

    说明:

    (1)静态成员函数也从属于类,由同一个类的所有对象共同拥有。

    (2)静态成员函数只能直接访问该类的静态成员变量、静态成员函数以及类以外的数据和函数,而访问非静态成员必须通过参数传递方式得到类的对象,然后通过对象名来访问。

    (3)在类内可以直接通过函数名称访问。

    (4)在类外可以通过 类名::静态成员函数(实参)、对象.静态成员函数(实参)、指针->静态成员函数(实参) 来访问。

 

class Point
{
    int x, y;
    static int no;    //声明静态成员变量
public:
    Point(int a, int b) : x(a), y(b) { ++no; }
    static void print() {     //静态成员函数
        std::cout << “no=” << no << std::endl;
    }
};
int Point::no = 0; 
void main(){
    Point pt(1, 10);
    Point* p1 = new Point(3, 3);
    pt.print();       //对象名.静态成员函数名()
    p1->print();    //指针->静态成员函数名()
    Point::print(); //类名::静态成员函数名()
}
静态成员函数

 

 

 

六、函数的重载

  概念:函数重载是指在同一作用域内,可以有一组相同函数名不同参数列表的函数,这组函数被称为重载函数。

 

  作用:达到行为标识符统一

 

  判断依据函数名称、形参列表(参数类型或个数)和返回值无关

  示例

#include "stdafx.h"
#include <iostream>
using namespace std;

int  add(int a, int b)
{
    return a + b;
}

double  add(double a, double b)
{
    return a + b;
}

const char*  add(char *a,char *b)
{
    strcat_s(a,100,b);
    return a;
}

int main()
{
    char s[100] = {"hello "};
    cout << add(10, 10) << endl;
    cout << add(10.9, 10.5) << endl;
    cout << add(s, "world") << endl;
    return 0;
函数重载

 

  常成员函数:(1)在一个类中使用const关键字说明的成员函数,称为常成员函数

        (2)常成员函数不能修改成员变量的值,也不能调用该类中其它没有用const修饰的成员函数。

                            

 

#include "stdafx.h"
#include <iostream>
using namespace std;

class S
{
    int a;
public:
    S(int x) :a(x)
    {
    }

    void print()
    {
        cout << a << endl;
    }

    void print()const;
};

void S::print()const
{
    cout <<"const"<< a << endl;
}

int main()
{
    S a(10);
    a.print();//调用非常成员函数
    const S& a1 = a;
    a1.print();//调用常成员函数
    return 0;
}
View Code

 

运算符重载

  概念运算符重载就是赋予已有运算符多重含义

 

 

  语法格式:

 

      class 类名

 

      {

 

        public:

 

            类型 operator 运算符(形参列表)

 

            {  函数体 }

 

      };

 

  原则:

    (1)被重载的运算符不改变原来的操作数个数、优先级和结合性。

 

    (2)不能创新发明。

 

    (3)不能改变运算符对预定义类型(基本类型)的操作方式。

 

    (4)成员函数实现运算符重载时,运算符的左操作数为当前对象。

 

   (5)如果重载的运算符函数是双目的运算符,则参数表中有一个参数,若为单目运算符,则参数表中有零个参数(没有参数),特殊情况除外,比如++、--运算符。

  不可重载的运算符:

    

 

 

   实现方式:成员函数  和  友员函数

 

posted @ 2020-03-21 00:16  直至成伤  阅读(277)  评论(0编辑  收藏  举报