从零开始学C++(3)---类与对象(万字整理)

目录

1.面向对象程序设计思想

1.封装

2.继承

3.多态

2.初识类和对象

1.类的定义

2.对象的创建和使用

3.封装

4.this指针

5.构造函数

1.自定义构造函数

1.自定义无参构造函数

2.自定义有参构造函数

2.重载构造函数

3.含有成员对象的类的构造函数

6.析构函数

7.拷贝构造函数

1.拷贝构造函数的定义

2.浅拷贝

3.深拷贝

8.关键词修饰类的成员

1.const修饰类的成员

1.const修饰成员变量

2.const修饰成员函数

2.static修饰类的成员

1.static修饰成员变量

2.static修饰成员函数

9.友元

1.友元函数

1.普通函数作为友元函数

2.其他类的成员函数作为友元函数

2.友元类


1.面向对象程序设计思想

面向对象程序设计思想有三大特征:封装,继承,多态。

1.封装

        封装是面向对象思想最重要的特征,封装就是隐藏,它将数据和数据处理过程封装成一个独立的模块,将对象的属性和行为都封装起来,对外提供接口,不需要外界知道具体的实现细节。

2.继承

        继承主要描述的是类与类之间的关系,通过继承无须重新编写原有类,就能对原有类进行扩展,继承不仅增强了代码复用性,提高了开发效率,为程序维护提供便利。

3.多态

        多态是指事物的多种表现形态,多态就是不同的对象同一信息产生不同的行为,多态使得开发更科学,更符合人类的思维习惯,提高软件可靠性。

2.初识类和对象

1.类的定义

        类是对象的抽象,是一种自定义数据类型,用于描述一组对象的共同属性和行为。类的定义如下:

class 类名
{
权限控制符:
    成员;
};

(1)class是类定义的关键字。

(2)类名是类的标识符,符合命名规范,

(3)类名后面的大括号,用于包含类的成员,类的所有成员要在大括号中声明,类可以定义成员变量和成员方法,成员变量描述对象的属性,成员函数描述对象的行为。

(4)声明类的成员时,要使用权限控制符限定成员的访问规则,包括public,protected,private,权限依次递减。

(5)大括号后的‘;’表示类定义的结束。

class Student 
{
public:
    void study();
    void exam();
private:
    string _name;
    int _age;
};

通常类的成员函数在类中声明,在类外实现,在类外实现成员,需在返回值后函数名前加上所属的类作用域,表示函数属于哪个类。

void Student::study()
{
    cout<<"学习C++"<<endl;
}
void Student::exam()
{
    cout<<"C++考试成绩100分"<<endl;
}
  • public(公有类型):被public修饰的成员称为公有成员,可以被所属类的成员函数,类对象,派生类对象,友元函数,友元类访问。
  • protected(保护类型):可以被所属类的成员函数,派生类对象,友元类和友元函数访问。
  • private(私有类型):只能被所属类的成员函数,友元函数,友元类访问。

2.对象的创建和使用

对象的定义格式如下:

类名 对象名;

下面创建一个学生类的对象:

Student stu;

创建了类的对象stu之后,系统要为对象分配内存空间,用于存储对象成员,每个对象都有成员变量和成员方法,创建两个Student类对象stu1和stu2,每个学生的属性都不同,在创建对象时应当为每个对象分配独立的空间存储成员变量,c++用同一块空间存放同类对象的成员函数代码,每个对象调用同一段代码。

对象的成员变量和成员函数的访问可以通过‘.’运算符实现,格式如下:

对象名.成员变量
对象名.成员函数
#include <iostream>
#include <string>
using namespace std;
class Student
{
public:
    void study();
    void exam();
    string _name;
    int _age;
};
void Student::study()
{
    cout<<"学习C++"<<endl;
}
void Student::exam()
{
    cout<<"C++考试成绩100分"<<endl;
}
int main()
{
    Student stu;
    stu._name="liu";
    stu._age=20;
    cout<<stu._name<<stu._age<<endl;
    stu.study();
    stu.exam();
    return 0;
}
liu20
学习C++
C++考试成绩100分

可以使用new创建类对象

Student* ps=new Student;
delete ps;

3.封装

        C++中的封装是通过类实现的,通过类把具体事物抽象成一个由属性和行为结合的独立单位,类的对象会表现出具体的属性和行为,封装是隐藏对象的内部实现细节,只对外提供访问的接口。

        通过权限控制符可以限制外界对类的成员变量的访问,将对象的状态信息隐藏在对象内部,通过类实现的接口实现对类中成员的访问。

#include <iostream>
#include <string>
using namespace std;
class Student
{
public:
    void study();
    void exam();
    void setName(string name);
    void setAge(int age);
    string getName();
    int getAge();
private:
    string _name;
    int _age;
};
void Student::study()
{
    cout<<"学习C++"<<endl;
}
void Student::exam()
{
    cout<<"C++考试成绩100分"<<endl;
}
void Student::setName(string name)
{
    _name=name;
}
void Student::setAge(int age)
{
    if(age<0||age>100)
    {
        cout<<_name<<"年龄输入错误"<<endl;
        _age=0;
    }
    else
        _age=age;
}
string Student::getName()
{
    return _name;
}
int Student::getAge()
{
    return _age;
}
int main()
{
    Student stu;
    stu.setName("liu");
    stu.setAge(-20);
    cout<<stu.getName() << stu.getAge()<<"岁"<<endl;
    stu.study();
    stu.exam();
    stu.setName("zhang");
    stu.setAge(20);
    cout<<stu.getName() << stu.getAge()<<"岁"<<endl;
    stu.study();
    stu.exam();
    return 0;
}
liu年龄输入错误
liu0岁
学习C++
C++考试成绩100分
zhang20岁
学习C++
C++考试成绩100分

4.this指针

在上面的例子中,getName函数可以区分是哪一个对象调用它,这是通过this指针实现的,

this指针是C++实现封装的一种机制,他将对象和对象调用的非静态成员函数联系在一起,当创建一个对象时,编译器会初始化一个this指针,指向创建的对象,this指针不存储在对象内部,而是作为所以非静态成员函数的参数。作为类的成员函数时,如果形参与类的属性重名,也可以用this指针解决。

void Student::setName(string name)
{
    this->_name=name;
}
void Student::setAge(int age)
{
    if(age<0||age>100)
    {
        cout<<_name<<"年龄输入错误"<<endl;
        _age=0;
    }
    else
        this->_age=age;
}

当类的成员返回值为一个对象,则可以使用return* this返回对象本身。

5.构造函数

构造函数是类的特殊成员函数,用于初始化对象,构造函数在创建对象时由编译器自动调用。C++的类中至少要有一个构造函数,如果类中没有定义。系统会提供一个默认的无参构造函数,函数体为空,因此一般要显示定义构造函数。

1.自定义构造函数

class 类名
{
权限控制符:
    构造函数名(参数列表)
    {
        函数体
    }
    //其它成员
};

格式要求如下:

  1. 构造函数名必须与类名相同
  2. 构造函数名前不需要返回类型
  3. 构造函数无返回值
  4. 构造函数一般为public

如果在类中提供了自定义构造函数,编译器不再提供默认构造函数。

1.自定义无参构造函数

自定义无参构造函数,可以在函数内部直接给成员变量赋值:

#include <iostream>
#include <iomanip>
using namespace std;
class Clock
{
public:
    Clock();//自定义无参构造
    void showTime();
private:
    int _hour;
    int _min;
    int _sec;
};
Clock::Clock()
{
    _hour=0;
    _min=0;
    _sec=0;
}
void Clock::showTime()
{
    cout<<setw(2)<<setfill('0')<<_hour<<":"
        <<setw(2)<<setfill('0')<<_min<<":"
        <<setw(2)<<setfill('0')<<_sec<<endl;
}
int main()
{
    Clock clock;
    cout<<"clock:";
    clock.showTime();
    return 0;
}
clock:00:00:00
2.自定义有参构造函数

如果希望在创建对象时提供有效的初始值,可以通过定义有参构造函数实现。

#include <iostream>
#include <iomanip>
using namespace std;
class Clock
{
public:
    Clock(int hour,int min,int sec);//自定义有参构造
    void showTime();
private:
    int _hour;
    int _min;
    int _sec;
};
Clock::Clock(int hour,int min,int sec)
{
    _hour=hour;
    _min=min;
    _sec=sec;
}
void Clock::showTime()
{
    cout<<setw(2)<<setfill('0')<<_hour<<":"
        <<setw(2)<<setfill('0')<<_min<<":"
        <<setw(2)<<setfill('0')<<_sec<<endl;
}
int main()
{
    Clock clock1(10,20,30);
    cout<<"clock1:";
    clock1.showTime();
    Clock clock2(22,16,12);
    cout<<"clock2:";
    clock2.showTime();
    return 0;
}
clock1:10:20:30
clock2:22:16:12

可以使用列表初始化实现成员变量初始化

Clock::Clock(int hour,int min,int sec):_hour(hour),_min(min),_sec(sec)
{

}

2.重载构造函数

在C++,构造函数允许重载。

class Clock
{
public:
    Clock();
    Clock(int hour);
    Clock(int hour,int min);
    Clock(int hour,int min,int sec);
private:
    int _hour;
    int _min;
    int _sec;
};

当定义具有默认参数的重载构造函数时,要防止调用的二义性。

Clock::Clock(int hour,int min):_hour(hour),_min(min)
{
    cout<<"调用两个参数的构造函数"<<endl;
    _sec=10;
}
Clock::Clock(int hour, int min, int sec=0)
{
    cout<<"调用三个参数的构造函数"<<endl;
    _hour=hour;
    _min=min;
    _sec=sec;
}

3.含有成员对象的类的构造函数

C++允许将一个对象作为另一个类的成员变量,即类中的成员变量可以是其他类的对象,称为类的子对象或成员对象。

class B
{
    A a;
}

创建含有成员对象的对象时,先执行成员对象的构造函数,再执行类的构造函数,创建B对象时,先执行类A的构造函数,再执行类B的构造函数,如果类A构造函数有参数,其参数要从类B的构造函数中传入,且必须以列表初始化。

#include <iostream>
using namespace std;
class Birth
{
public:
    Birth(int year,int month,int day);
    void show();
private:
    int _year;
    int _month;
    int _day;
};
Birth::Birth(int year, int month, int day)
:_year(year),_month(month),_day(day)
{
    cout<<"Birth类构造函数"<<endl;
}
void Birth::show()
{
    cout<<"出生日期"<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
class Student
{
public:
    Student(string name,int id,int year,int month,int day);
    void show();
private:
    string _name;
    int _id;
    Birth birth;
};
Student::Student(string name, int id, int year, int month, int day)
        : birth(year,month,day)
{
    cout<<"Student类构造函数"<<endl;
    _name=name;
    _id=id;
}
void Student::show()
{
    cout<<"姓名:"<<_name<<endl;
    cout<<"学号:"<<_id<<endl;
    birth.show();
}
int main()
{
    Student stu("lili",1002,2000,1,1);
    stu.show();
    return 0;
}
Birth类构造函数
Student类构造函数
姓名:lili
学号:1002
出生日期2000-1-1

6.析构函数

        在C++中,对象资源的释放通过析构函数完成,析构函数的作用是在对象被释放之前完成一些清理工作,调用完成后,对象占用的资源也被释放。与构造函数一样,析构函数也是类的一个特殊成员函数,格式如下:

class 类名
{
    ~析构函数名();
    ...
}
  • 析构函数的名称与类名相同,在析构函数名称前加‘~’符号。
  • 析构函数没有参数,所以不能重载。
  • 析构函数没有返回值,不能在内部return任何值。

        当程序结束时,编译器会自动调用析构函数完成清理工作,如果类中没有定义,编译器提供一个默认的析构函数,它只能完成栈内存对象的清理,不能完成堆内存的清理,往往要自己定义析构函数,函数中定义对象,在函数调用结束时,调用析构清理,static修饰对象在程序结束时调用析构,new创建对象,在delete时调用析构。

        析构函数的调用顺序与构造函数的调用顺序是相反的,先构造的后析构,后构造的先析构。

#include <iostream>
#include <cstring>
using namespace std;
class Rabbit
{
public:
    Rabbit(string name,const char* pf);
    void eat();
    ~Rabbit();
private:
    string _name;
    char* _food;
};
Rabbit::Rabbit(string name, const char *pf)
{
    cout<<"调用构造函数"<<endl;
    _name=name;
    _food=new char[50];//为food指针申请空间
    memset(_food,0,50);//初始化_food空间
    strcpy(_food,pf);//将pf指向的数据复制到_food中
}
void Rabbit::eat()
{
    cout<<_name<<" is eating "<<_food<<endl;
}
Rabbit::~Rabbit()
{
    cout<<"调用析构函数,析构"<<_name<<endl;
    if(_food!= nullptr)
        delete []_food;
}
int main()
{
    Rabbit A("A","luobo");
    A.eat();
    Rabbit B("B","baicai");
    B.eat();
    return 0;
}
调用构造函数
A is eating luobo
调用构造函数
B is eating baicai
调用析构函数,析构B
调用析构函数,析构A

7.拷贝构造函数

在程序中经常使用已有对象完成新对象的初始化,通过拷贝构造完成。

1.拷贝构造函数的定义

        拷贝构造函数是一种特殊的构造函数,具有构造函数的所以性质,使用本类对象的引用作为形参,能够通过一个已经存在的对象初始化该类的另一个对象。格式如下:

class 类名
{
public:
    构造函数名称(const 类名& 对象名)
    {
        函数体
    }
}

在定义拷贝构造函数时,为了使引用的对象不被修改,通常用const修饰引用的对象。

#include <iostream>
using namespace std;
class Sheep
{
public:
    Sheep(string name,string color);
    Sheep(const Sheep& another);
    void show();
    ~Sheep();
private:
    string _name;
    string _color;
};
Sheep::Sheep(string name,string color)
{
    cout<<"调用构造函数"<<endl;
    _name=name;
    _color=color;
}
Sheep::Sheep(const Sheep& another)
{
    cout<<"调用拷贝构造函数"<<endl;
    _name=another._name;
    _color=another._color;
}

void Sheep::show()
{
    cout<<_name<<" "<<_color<<endl;
}
Sheep::~Sheep()
{
    cout<<"调用析构函数"<<endl;
}
int main()
{
    Sheep sheepA("Dolly","white");
    cout<<"sheepA:";
    sheepA.show();
    Sheep sheepB(sheepA);
    cout<<"sheepB:";
    sheepB.show();
    return 0;
}
调用构造函数
sheepA:Dolly white
调用拷贝构造函数
sheepB:Dolly white
调用析构函数
调用析构函数

当涉及对象之间的赋值时,编译器会自动调用拷贝构造函数。

  1. 使用一个对象初始化另一个对象。
  2. 对象作为参数传递给函数。
  3. 函数返回值为对象。

2.浅拷贝

        如果没有定义拷贝构造函数,编译器会提供一个默认的拷贝构造函数,只能完成简单的赋值操作,无法完成堆内存成员数据的拷贝,类中含有指针时,只是将新对象的指针指向原有对象指针指向的内存,并没有申请新空间,称为浅拷贝。

#include <iostream>
#include <cstring>
using namespace std;
class Sheep
{
public:
    Sheep(string name,string color,const char* home);
    Sheep(const Sheep& another);
    void show();
    ~Sheep();
private:
    string _name;
    string _color;
    char* _home;
};
Sheep::Sheep(string name,string color,const char* home)
{
    cout<<"调用构造函数"<<endl;
    _name=name;
    _color=color;
    int len=strlen(home)+1;
    _home=new char[len];
    memset(_home,0,len);
    strcpy(_home,home);
}
Sheep::Sheep(const Sheep& another)
{
    cout<<"调用拷贝构造函数"<<endl;
    _name=another._name;
    _color=another._color;
    _home=another._home;
}

void Sheep::show()
{
    cout<<_name<<" "<<_color<<_home<<endl;
}
Sheep::~Sheep()
{
    cout<<"调用析构函数"<<endl;
    if(_home!= nullptr)
        delete []_home;
}
int main()
{
    const char* p="Beijing";
    Sheep sheepA("Dolly","white",p);
    cout<<"sheepA:";
    sheepA.show();
    Sheep sheepB(sheepA);
    cout<<"sheepB:";
    sheepB.show();
    return 0;
}

运行上面的程序,会抛出异常,在浅拷贝过程中,对象sheepA中的_home指针和sheepB中_home指针指向同一块内存空间,析构函数先析构B,后析构A,重复释放同一块内存空间,发生异常。

3.深拷贝

        深拷贝会为新对象中的指针分配新的内存空间,将数据复制到新空间。

Sheep::Sheep(const Sheep& another)
{
    cout<<"调用拷贝构造函数"<<endl;
    _name=another._name;
    _color=another._color;
    int len=strlen(another._home)+1;
    _home=new char[len];
    strcpy(_home,another._home);
}

8.关键词修饰类的成员

1.const修饰类的成员

1.const修饰成员变量

        使用const修饰的成员变量称为常成员变量,只可以读取第一次初始化的数据,之后不能修改。

2.const修饰成员函数

        修饰成员函数时,const位于成员函数的后面,在函数内部,只能访问类的成员变量,不能修改,而且只能调用类的常成员函数,类中定义的成员函数若与常成员函数名相同则构成重载,常成员函数只能由const修饰的对象进行访问。

2.static修饰类的成员

1.static修饰成员变量

        static修饰的静态成员变量只能在类内部定义,在类外初始化,可以通过对象和类进行访问,计算类的大小时不包含其中。

2.static修饰成员函数

        使用static修饰的成员函数,同静态成员变量一样可以通过对象或类调用,静态成员函数可以访问类中的静态成员变量和静态成员函数,静态成员函数属于类,不属于对象,没有this指针。

9.友元

1.友元函数

        友元函数可以是类外定义的函数或者是其他类中的成员函数,在类中声明某一函数为友元函数,则函数可以访问类中所有数据。

1.普通函数作为友元函数
class 类名
{
    friend 返回值 友元函数名(形参列表);
}
#include <iostream>
using namespace std;
class Circle
{
    friend void getArea(Circle &circle);
private:
    float _radius;
    const float PI=3.14;
public:
    Circle(float radius);
    ~Circle();
};
Circle::Circle(float radius):_radius(radius)
{
    cout<<"初始化圆的半径"<<_radius<<endl;
}
Circle::~Circle() {}
void getArea(Circle &circle)
{
    cout<<"圆的半径是:"<<circle._radius<<endl;
    cout<<"圆的面积是"<<circle.PI*circle._radius*circle._radius<<endl;
    cout<<"友元函数修改半径"<<endl;
    circle._radius=1;
    cout<<"圆的半径是:"<<circle._radius<<endl;
}
int main()
{
    Circle circle(10);
    getArea(circle);
    return 0;
}
2.其他类的成员函数作为友元函数

        其他类中的成员函数作为本类的友元函数时,需要在本类中表明该函数的作用域,并添加友元函数所在类的前向声明。

class B;
class A
{
public:
    int func();
};
class B
{
    friend int A::func();
};

2.友元类

        可以将一个类声明为友元类,友元类可以声明在类中任意位置,声明友元类之后,友元类中的所有成员函数都是该类的友元函数,能够访问该类的所有成员。

class B;
class A
{
};
class B
{
    friend class A;
};
#include <iostream>
using namespace std;
class Time
{
public:
    Time(int hour,int minute,int second);
    friend class Date;
private:
    int _hour;
    int _minute;
    int _second;
};
class Date
{
public:
    Date(int year,int month,int day);
    void showTime(Time& time);
private:
    int _year;
    int _month;
    int _day;
};
Date::Date(int year, int month, int day)
{
    _year=year;
    _month=month;
    _day=day;
}
void Date::showTime(Time &time)
{
    cout<<_year<<"-"<<_month<<"-"<<_day
        <<" "<<time._hour<<":"<<time._minute
        <<":"<<time._second<<endl;
}
Time::Time(int hour, int minute, int second)
{
    _hour=hour;
    _minute=minute;
    _second=second;
}
int main()
{
    Time time(17,30,20);
    Date date(2019,10,31);
    date.showTime(time);
    return 0;
}
2019-10-31 17:30:20
  1. 友元声明位置不受权限控制符的影响。
  2. 友元是单向的。
  3. 友元不具有传递性。
  4. 友元关系不能被继承。

持续更新中............

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值