对类和对象的详细解析

目录

1.类的构成

2.this指针

 3.C++类里面的静态成员

 3.1 类里面的静态成员函数

3.2 类里面的静态成员变量

静态成员变量的特点

共享性

存储位置

生命周期

访问权限

如何初始化?

 构造函数


1.类的构成

 public修饰的成员在类外可以直接被访问

private和protected修饰的成员不能在类外被访问

如果变量没有被这三个修饰词来修饰的话,则默认为private

2.this指针

关于this指针的一个经典回答:

有一个房子类

当你进入这个房子的时候,你可以看见桌子、椅子等,但是房子的全貌你看不见了

对于一个类的实例来说,你可以看见他的成员函数、成员变量

但是实例本身呢?

this这个指针就时时刻刻指向这个实例本身

this指针是指向对象的指针,存放对象的首地址

#include <iostream>
using namespace std;
class Student
{
public:
    void Setage(int age)
    {
        this->age = age;//this->age表示这个类所指向的age,单独的age是形参
    }
    void Showage()
    {
        cout << age << endl;
    }

private:
    int age;
};
int main()
{
    Student s;
    s.Setage(18);
    s.Showage();
    return 0;
}
//输出结果为18
#include <iostream>
using namespace std;
class Student
{
public:
    void Setage(int age)
    {
        age = age;//age=age,并不会改变类里面age的值
    }
    void Showage()
    {
        cout << age << endl;
    }

private:
    int age;
};
int main()
{
    Student s;
    s.Setage(18);
    s.Showage();
    return 0;
}
//输出结果是0

this指针是编译器帮我们加上的,不占用空间

#include <iostream>
using namespace std;
class Student
{
private:
    int age;
};

int main()
{
    cout << sizeof(Student) << endl;//结果为4
    return 0;
}

既然this指针指向类,那么类的地址和this的地址是一样的

#include <iostream>
using namespace std;
class Student
{
public:
    void test()
    {
        cout << this << endl;
    }

private:
    int age;
};

int main()
{
    Student s;
    s.test();
    cout << &s << endl;
    return 0;
}

输出的地址是一样的

 3.C++类里面的静态成员

静态成员是属于整个类的而不是某个对象,静态成员只存储一份供所有对象使用

 3.1 类里面的静态成员函数

静态成员函数没有隐藏的this指针

#include <iostream>
using namespace std;
class Student
{
public:
    void Set(int age)
    {
        this->age = age;
    }
    void Showage()
    {
        cout << age << endl;
    }
    static void test()
    {
        cout<<this->age<<endl;//这个地方的age会报错,如果不加this也会报错
        //静态成员函数不能引用非静态成员,但是可以在类的非静态成员函数里面使用静态成员
    }
private:
    int age;
};

int main()
{
    Student s;
    return 0;
}

这就说明了静态函数是在这个类创建出来之前就出现了,也就是说静态函数的创建时间比这个类早,所以在这个静态函数里面使用类里面的东西会报错

那么应该如何使用这个函数呢

首先要注意一点:这个静态函数不能使用类里面的东西

class Student
{
    static void test()
    {
        cout<<"i want to sleep"<<endl;
    }
};

int main()
{
    Student s;
    Student::test();//这里记住不能通过类名调用非静态成员函数
    s.test();
    //上面两句说明类的对象可以使用静态和非静态成员函数
    return 0;
}

这两种方法都可以调用这个静态函数

3.2 类里面的静态成员变量

静态成员变量在类的所有实例之间共享,并且可以在不创建类的情况下访问(在类创建之前就创建好了

3.3静态成员变量的特点

共享性

静态成员变量在类所有的实例之间共享,这个类的每个对象的静态变量都是相同的

存储位置

静态成员变量存储在静态存储区,而不是每个对象的堆栈中。这使得他们可以在不创建类的实例的情况下访问

生命周期

与程序的生命周期一样。在程序启动的时候就创建了,在程序结束的时候就销毁

访问权限

静态成员变量可以通过类名来访问,也可以通过对象来访问。但是建议前者,这样子能强调他们的共享性

如何初始化?

class Student
{
private:
    static int a;
};

int Student::a=0;

 4构造函数

构造函数的主要作用不是开辟空间创建对象,而是为了初始化对象

特征

他不是开空间创建对象,而是初始化对象

1. 无返回值(也不用写void)

2. 函数名与类名相同

3. 构造函数可以重载

#include <iostream>

using namespace std;

class Student
{
public:
    Student(int a,int b,int c)//构造函数
    {
        _a=a;
        _b=b;
        _c=c;
    }
    Student(int a,int b)
    {
        _a=a;
        _b=b;
    }
    void Show()
    {
        printf("%d %d %d",_a,_b,_c);
    }
private:
    int _a,_b,_c;
};

int main()
{
    Student s(1,2,3);//创建s对象的同时调用构造函数
    Student p(1,2);//函数重载
    s.Show();
    return 0;
}
//输出结果1 2 3
#include <iostream>

using namespace std;

class Student
{
public:
    void Show()
    {
        printf("%d %d %d",_a,_b,_c);
    }
private:
    int _a,_b,_c;
};

int main()
{
    Student s;
    s.Show();
    return 0;
}
/*
输出结果32767 0 0
如果类里面没有构造函数,则C++编译器会自动调用一个无参的默认构造函数
*/
private:
    int _a=1,_b=1,_c=1;

/*
这里对_a,_b,_c不是赋值不是初始化,而是给缺省值
原因:这里的变量都是声明,还没有创建出来
*/

5析构函数

作用与构造函数相反。在对象销毁时自动调用,完成对象中资源的清理工作

特征

析构函数名是在类名的签名加~

无参数无返回值

一个类只能有一个析构函数,如果自己没有定义,系统会调用默认的析构函数。析构函数不能重载

对象生命周期结束时调用

析构函数的执行在return之前

#include <iostream>
using namespace std;
class S
{
public:
    S()
    {
        cout << "构造" << endl;
    }
    ~S()
    {
        cout << "析构" << endl;
    }
};
int main()
{
    S s;
    return 0;
}

先输出构造,然后再输出析构

 作用

当我们在类中声明了一些指针变量的时候,我们一般在析构函数里面释放这些指针变量所占有的空间,因为系统不会释放指针变量指向的空间,我们需要自己来delete

6.构造函数和析构函数的一些应用

6.1 定义为局部变量

先补充一个知识点:局部变量存储在栈区,全局变量和静态变量存储在静态区

#include <iostream>

using namespace std;

class Date
{
public:
    Date(int a)
    {
        _a = a;
        cout << "Date()->" << _a << endl;
    }
    ~Date()
    {
        cout << "~Date()->" << _a << endl;
    }

private:
    int _a;
};

int main()
{
    Date a(1);
    Date b(2);
    return 0;
}

输出结果

析构函数的时候为什么先输出~Date()->2呢?

首先我们要知道a和b变量都是局部变量,都存储在栈区,栈区遵循后进先出的原则

 6.2 局部变量和全局变量同时存在时

#include <iostream>

using namespace std;

class Date
{
public:
    Date(int a)
    {
        _a = a;
        cout << "Date()->" << _a << endl;
    }
    ~Date()
    {
        cout << "~Date()->" << _a << endl;
    }

private:
    int _a;
};

Date b(2);

int main()
{
    Date a(1);
    return 0;
}

输出结果

全局变量先定义,在程序结束之后再销毁(在局部变量销毁之后再销毁)

 6.3 静态变量、局部变量、全局变量共存

#include <iostream>

using namespace std;

class Date
{
public:
    Date(int a)
    {
        _a = a;
        cout << "Date()->" << _a << endl;
    }
    ~Date()
    {
        cout << "~Date()->" << _a << endl;
    }

private:
    int _a;
};

Date b(2);

int main()
{
    Date a(1);
    static Date c(3);
    return 0;
}

输出结果

创建变量按顺序来。首先是局部变量被销毁,静态区的变量(只考虑这个区域时)在销毁时也是类似“后进先出”一样来销毁

 6.4 静态变量在循环中

#include <iostream>

using namespace std;

class Date
{
public:
    Date(int a)
    {
        _a = a;
        cout << "Date()->" << _a << endl;
    }
    ~Date()
    {
        cout << "~Date()->" << _a << endl;
    }

private:
    int _a;
};

int main()
{
    for (int i = 0; i <= 1; i++)
    {
        static Date a(1);
        Date b(2);
    }
    return 0;
}

输出结果

静态变量只会被创建一次,所以只有一次构造函数和一次析构函数

 7.拷贝构造函数

浅拷贝

拷贝成员变量的值

#include <iostream>

using namespace std;

class Student
{
public:
    Student(int a, int b, int c)
    {
        _a = a;
        _b = b;
        _c = c;
    }
    /*
    当有Student b(a)这行代码的时候
    问题1:为什么要写成 Student &s呢?
    写成Student &s表示s是a的别名,不需要调用拷贝构造函数
    如果写成Student s的话,会无限递归
    原因:在调用拷贝构造函数的时候,
Student(Student s)
    {
        this->_a = s._a;
        this->_b = s._b;
        this->_c = s._c;
    }
把a传给s也会调用拷贝够咱函数,也就是说,调用一个拷贝构造函数会使得其内部嵌套另一个拷贝构造函数
那这个嵌套的拷贝构造函数也会继续嵌套一个,依次无限嵌套下去而导致死循环;所以要加一个&
    问题2:那为什么加一个const呢?
    因为我们要a的值不变,将其内部的值赋给b(s),所以我们最好加一个const来修饰来让a的值不变
只要a里面的值变,就会报错
因为有可能出现以下情况
Student(const Student &s)
    {
        a._a = this->_a;
        a._b = this->_b;
        a._c = this->_c;
    }
如果没有const,出现上述情况编译器是不会报错的
    */
    Student(const Student &s)
    {
        this->_a = s._a;
        this->_b = s._b;
        this->_c = s._c;
    }
    void Show()
    {
        printf("%d %d %dn", _a, _b, _c);
    }

private:
    int _a, _b, _c;
};

int main()
{
    Student a(10, 20, 30);
    Student b(a);
    b.Show();
    return 0;
}

输出结果

 8.重载

关键字:operator

8.1 运算符重载

#include <iostream>
using namespace std;
class Student
{
public:
    Student(int a, int b, int c)
    {
        _a = a;
        _b = b;
        _c = c;
    }
    bool operator==(const Student &b)//重载==运算符
    {
        return _a == b._a && _b == b._b && _c == b._c;//a和b相等的条件
    }
private:
    int _a, _b, _c;
};

int main()
{
    Student a(10, 20, 30);
    Student b(10, 20, 30);
    cout << (a == b) << endl;//这里判断a和b是否相等只需要判断a和b里面的_a,_b,_c是否分别相等
    return 0;
}
#include <iostream>
using namespace std;
class Student
{
public:
    Student(int a, int b, int c)
    {
        _a = a;
        _b = b;
        _c = c;
    }
    int operator-(const Student &b)
    {
        return b._c - _c;
    }

private:
    int _a, _b, _c;
};

int main()
{
    Student a(10, 20, 30);
    Student b(10, 20, 50);
    cout << (b - a) << endl;
    return 0;
}
//输出结果是a._c - b._c  结果为-20

这里就有一个问题

void operator++是怎么区别前置++和后置++的呢?

int operator++();//前置++
    int operator++(int);//后置++
//规定:()里面无参数的为前置++,否则为后置++

注意点

#include <iostream>
using namespace std;
class A
{
public:
    A(int val){
        _val=val;
    }
    void operator+(const A& d){
        _val+=d._val;
    }
    void operator=(const A& d){
        _val=d._val;
    }
    void Print(){
        cout<<_val<<endl;
    }
private:
    int _val;
};
int main()
{
    A a(3),b(4),c(7);
    c=a=b;
/*
c=a=b会报错,但是a=b,不会报错
因为c=a=b这句话首先执行的是a=b,然后返回值是void
所以
void operator=(const A& d){
        _val=d._val;
    }
应该改成
A operator=(const A& d){
        _val=d._val;
        return *this;//这里返回值是*this的一个拷贝(拷贝的话可能调用其中的拷贝函数或者构造函数什么的)
    }
所以最好写成
A& operator=(const A& d){
        _val=d._val;
        return *this;//这里返回的是*this的别名,不会再去调用拷贝函数
    }
*/
    a.Print();
    return 0;
}

8.2 流重载

#include <iostream>

using namespace std;

class Date
{
public:
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void operator<<(ostream &out) // out是cout的别名
    {
        out << _year << '/' << _month << '/' << _day << endl;
    }
    void operator>>(istream &in)
    {
        in>>_year>>_month>>_day;
    }

private:
    int _year, _month, _day;
};

int main()
{
    Date a(2023, 9, 16);
    a>>cin;//a是被操作的对象,cin传给in
    a<<cout;
    return 0;
}

const的参与

#include <iostream>
using namespace std;
class Date
{
public:
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()
    {
        printf("%d %d %dn", _year, _month, _day);
    }

private:
    int _year, _month, _day;
};
int main()
{
    Date a(2023, 9, 17);
    const Date b(1,1,1);
    a.Print();
    b.Print();//这里会报错
/*
因为b是一个const Date类型的
b.Print()的原型是b.Print(&b),&b是一个const Date*类型的
void Print()的原型是void Print(Date* const this)
&b从const Date*变成Date*权限放大了
所以我们将void Print()写成void Print() const,这样他的原型变成了:
void Print(const Date* const this)是const Date*类型,后面那个const是修饰this
让this一直指向这个类且不能变
*/
    return 0;
}
#include <iostream>
using namespace std;
class Date
{
public:
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()const
    {
        printf("%d %d %dn", _year, _month, _day);
    }
    bool operator<(const Date& d)
    {
        return _year<d._year;
    }

private:
    int _year, _month, _day;
};
int main()
{
    Date a(2023, 9, 17);
    const Date b(1,1,1);
    a<b;//不会报错
    b<a;//这里会报错
/*
bool operator<(const Date& d)的原型是
bool operator<Date* const this,const Date& d)
所以写成b<a的时候(b是const Date*类型)
b由const Date*变成Date*,权限变大
可以写成
bool operator<(const Date& d)const
*/
    return 0;
}

 9.初始化列表

初始化列表可以认为是成员变量定义的地方

(private里面是声明)

9.1格式

#include <iostream>
using namespace std;
class Date
{
public:
//格式如下
    Date(int a)
        : _a(a),
          _b(a)
    {
    }
    void Print()
    {
        cout << _a << endl;
    }

private:
    int _a;
    int &_b;
};
int main()
{
    Date a(6);
    a.Print();
    return 0;
}

9.2例子

每个成员变量在初始化列表中只能出现一次

类中包含以下成员,必须放在初始化列表位置进行初始化

1.引用成员变量

2.const成员变量

3.自定义类型成员

class A
{
public:
    A(int a)
        : _a(a)
    {
    }
    void Print()
    {
        cout << _a << endl;
    }

private:
    int _a;
};

class B
{
public:
    B(int a, int b)
        : _b(b),
          _a(a),
          _n(10),
          _r(b)
    {
    }
    void Print()
    {
        _a.Print();
        cout << _b << endl;
    }

private:
    int _b;
    A _a;
    const int _n;
    int& _r;
};

 成员变量在类中声明的次序就是其在初始化列表里面初始化的次序,与其在初始话列表里面的先后顺序无关

例子:

#include <iostream>

using namespace std;

class A
{
public:
    A(int a)
    :_a(a),
    _b(_a)
    {}
    void Print()
    {
        cout<<_a<<endl;
        cout<<_b<<endl;
    }
private:
    int _b;
    int _a;
};

int main()
{
    A a(3);
    a.Print();
    return 0;
}
/*
输出结果是 3 随机值
因为先是把_a赋值给_b,再把a赋值给_a
*/

9.3explicit关键字

用explicit修饰的构造函数,会禁止单参构造函数的隐式转换

首先搞明白什么是隐式转换

class A
{
public:
    A(int a)//单参构造函数
        : _a(a)
    {
    }

private:
    int _a;
};

//定义一个类对象
A a(10);
/*
此时_a的值为10,如果进行以下操作,a=20,这样子会把_a的值变为20
编译器会用20(将20赋值给无名对象的_a)构造一个无名对象
最后用无名对象赋值给对象a,这种转换就叫隐式转换
*/

隐式转换其实还有很多,比如

int a=2;

double b=a;

编译器会先引入一个中间变量,类型为double,然后再将这个值赋值给b

#include <iostream>

using namespace std;

class A
{
public:
    A(int a)
/*
如果这里改成explicit A(int a)的话
a=5那行代码会报错
因为explicit会禁止单参构造函数的隐式转换
*/
        : _a(a)
    {
    }
    void Print()
    {
        cout << _a << endl;
    }

private:
    int _a;
};

int main()
{
    A a(3);
    a = 5;
    a.Print();
    return 0;
}

 9.4 匿名对象

生命周期只有它所在的那一行

class A
{
public:
    A(int a)
    :_a=a
    {}
private:
    int _a;
}; 

A a(10);
    a = A(20);
    a.Print();

如果不使用匿名对象,那就需要:
A a(10);
A b(20);
a=b;
这样子来操作
/*
A(20)表示创建一个匿名对象,他的_a值为20
这个匿名对象创建的时候也会调用构造函数和析构函数
*/

10.友元类

如果类A是类B的友元类,那么B可以调用A里面的private成员和protected成员

但是这样子做会破坏类的封装性

注意点

1. 友元关系不能被继承

2. 友元关系是单向的,A是B的友元,但是B不一定是A的友元

3. 友元不具有传递性,比如A是B的友元,B是C的友元,但是A不是C的友元

#include <iostream>

using namespace std;

class A
{
public:
    friend class B;//代表A是B的友元类,B可以访问A里面的所有成员
    A(int a)
    {
        _a=a;
    }
private:
    int _a;
};

class B
{
public:
    B(int b)
    {
        _b=b;
    }
    void Show_A_a(A a)
    {
        cout<<a._a<<endl;//这里就体现了可以访问A定义的对象a里面的私有成员
    }
private:
    int _b;
};

int main()
{
    A a(666);
    B b(555);
    b.Show_A_a(a);
    return 0;
}

10. 2内部类

定义:如果一个类定义在另一个类的里面,那这个里面的类就叫做内部类

内部类是外部内的友元类,但是外部类不是内部类的友元类

比如A是外部类,B是外部类

那么定义一个类的时候应该A::B b;定义一个对象b,b能调用A和B里面的成员,但是如果A a,那a不能调用B里面的成员