C++入门篇之类和对象总结

Posted 捕获一只小肚皮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++入门篇之类和对象总结相关的知识,希望对你有一定的参考价值。

前言

本篇文章将会主要讲解构造函数的初始化列表,static成员,以及内部类,目的是对前几章讲解类时候的一个深入和总结.


再谈构造函数

我们上一节讲解构造函数时候,了解了构造函数的定义和使用,明白编译器会通过调用构造函数给类成员一个初始值,但是注意了~~,这个过程却并不是初始化,而是只能叫做赋初值,因为初始化只能一次,比如下面的一段代码:

class A {
public:
    A(int c = 10) {
        cout << "A(int c)" << endl;
        _c = c;
    }
private:
    int _c;
};

class B {
public:
    B(int a,int b,A& ca) {
        _a = a;
        _b = b;
        _ca = ca;
    }
private:
    int _a;
    int _b;
    A _ca;
};

int main() {
    A a(20);
    B b(1,2,a);
    return 0;
}

大家觉得上面的结果会是什么?没错,会打印两句A(int c),但是博主在这里有个小小问题,就这两句分别在哪一行代码后打印的呢?

答案:

  • 第一次定义A对象后,会调用一次构造函数,也就打印了第一句A(int c).
  • 第二次打印确是在程序B(int a,int b,A& ca)之后,程序_a = a之前,也就是在这两句之间,不信吗?我们下图为例:

当程序执行到_a = a以后,已经打印出了A(int c),那么这是为什么呢? 请看下一小节的初始化列表.


初始化列表

以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式

什么意思呢?如下:

class Date {
public:
    Date(int year, int month, int day) 
        :_year(year),_month(month),_day(day)
    {
    }
    void Print(){
        cout<<"year是"<<_year<<endl;
        cout<<"month是"<<_month<<endl;
        cout<<"day是"<<_day<<endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

int main(){
    Date date(2021,10,23);
    date.Print();
	return 0;
}

我们可以清晰地看到,通过构造函数的初始化列表,就已经成功初始化了类数据成员.所以现在大家应该已经明白了,为什么说初始化列表才是真正的初始化数据成员,而通过构造函数的函数体赋值形式叫做初次赋值.因为在函数体内部的赋值性质是可以修改初始化列表的结果,比如把构造函数改成下面这样:

    Date(int year, int month, int day) 
        :_year(year),_month(month),_day(day)
    {
            _year = 2000;
            _month = 1;
            _day = 1;
    }

那么刚才的结果将会是这样:


初始化列表注意点

①每个数据成员只能在初始化列表中出现一次(因为只能初始化一次),还是按照上面的Date类为例,若这样写就错误了:

Date(int year, int month, int day) 
    :_year(year),_month(month),_day(day),_year(2000),_month(10),_day(20)
{
}

②数据初始化顺序只和数据声明顺序有关,与初始化列表无关:

class A
{
public:
	A(int a)
		:_a1(a)
		,_a2(_a1)
		{}
	void Print() {
		cout<<_a1<<" "<<_a2<<endl;
	}
private:
	int _a2;
	int _a1;
};

该类的结果为:

我们能够发现,_a2的值并不是100,因为最开始初始化的数据就是_a2,但是给_a2的值缺是还没有初始化的_a1


③当类中含有以下三种数据成员时候,对他们初始化必须使用初始化列表,分别是:

  • 引用成员变量
  • const成员变量
  • 自定义类型成员变量(没有默认的构造函数时候)
class A
{
public:
    A(int a)
    :_a(a)
    {}
private:
    int _a;
};

class B
{
public:
    B(int a, int ref)
    :_aobj(a)
    ,_ref(ref)
    ,_n(10)
    {}
private:
    A _aobj; // 没有默认构造函数
    int& _ref; // 引用
    const int _n; // const
};

explicit关键字

他的作用说简单点就是不允许定义对象时候使用=形式初始化,比如:

class Date {
public:
    explicit Date(int n) 
    {
        _n = n;
    }
private:
    int _n;
};

int main()
{
    Date date1(100);  
    Date date2 = 100; //正常情况下,这样也是可以初始化的,但是加了explict就不可以.
    return 0;
}

static成员

概念:主要有两种,一种是修饰数据成员,称为静态成员变量.一种是修饰成员函数,称为静态成员函数.其中静态成员变量必须在类外进行初始化,并且静态成员大小不计算在类内.

其特性为:

静态成员为所有类对象所共享,不属于某个具体的实例

class Date {
 	public:
    Date(int b  = 10)
    {
        _a++;
        _b = b;
    }
    static void cntans()
    {
        cout<<_a<<endl;
    }
    private:
	static int _a;    
    int _b;
};

int Date::_a = 10;   //静态成员必须在外面初始化.

int main()
{
    Date d1;
    Date d2;
    Date d3;
    d1.cntans();
    d2.cntans();
    d3.cntans();
    return 0;
}

大家猜猜会打印什么呢?答案如下:

可以清晰的看到,三次打印都是13,原因就是因为静态成员由所有类对象共享,并不是属于某个具体对象


类静态成员即可用类名::静态成员或者对象.静态成员来访问

在上面我们已经使用过了对象.静态成员访问格式,现在就介绍一下类名::静态成员.

仍然以上面为例:

int main(){
	Date d1;
    Date::cntans();    //类名::静态成员格式
    return 0;
}

静态成员函数没有隐藏的this指针,不能访问任何非静态成员

我们仍然以上面Date类为例,比如下面的修改cntans函数的错误形式:

    static void cntans()
    {
        cout<<_a<<endl;  //_a是静态成员,类内访问没问题.
        cout<<_b<<endl; //但是_b是非静态成员,由于没有this指针,所以访问_b非法.
    }

静态成员和静态函数也有public、protected、private3种访问级别,也可以具有返回值

这个博主就不再赘述,大家自行检查.


友元

友元和静态很相似,具有两种.修饰函数的叫做友元函数,修饰类的叫做友元类.在我们介绍这个之前,先做一个小测验吧,我们利用之前学过的重载运算符,对<<或者>>进行重载,实现的类仍然是我们上面的日期类

友元函数

class Date
{
public:
    Date(int year, int month, int day)
    : _year(year)
    , _month(month)
    , _day(day)
    {}
    
    ostream& operator<<(ostream& out)
    {
    	out<<_year<<"-"<<_month<<"-"<<_day;
    	return cout;
    }
private:
    int _year;
    int _month;
    int _day;
};

大家觉得我们这样进行重载,会不会有什么问题呢?我们先调用一下试试吧.

Date date(2021,10,10);
cout << date;

我们进行编译,运行然后就能发现结果报错:

为什么呢?我们之前在讲运算符重载时候强调过,我们在写参数列表时,需要按照运算符的操作数格式进行重载.但是这里呢>我们按照了吗,并没有,因为这里有隐藏的this指针,也就是说操作数弄反位置了.那怎么进行修改呢?目前我们的办法只有一个,那就是放到全局:

ostream& operator<<(ostream& out,Date& date)
{
    out << _year << "-" << _month << "-" << _day;
    return cout;
}

但是像这样又会遇到一个问题,那就是_year等成员是私有的,在外部无法访问.那又怎样进行解决呢?现在就是我们的老大哥—friend友元驾临.

这种情况我们只需要在类Date中的任何位置放一份重载函数声明,并在前面加上friend.

class Date
{
    
    friend ostream& operator<<(ostream& out,Date& date);  //一般习惯性的是加在这里
    
public:
    Date(int year, int month, int day)
    : _year(year)
    , _month(month)
    , _day(day)
    {}    
private:
    int _year;
    int _month;
    int _day;
};

注意:友元函数,只需要在函数的声明前加上friend就行,并且友元不属于任何类,只是为了突破类的作用域限制,以达到访问类的私有或保护成员.

并且友元函数不可以用const进行修饰,不受任何的类作用域符限制,而同一个友元函数还可以是多个类的友元.


友元类

友元类和友元函数相似,当一个类A是类B的友元类后,类A便可以访问B的任何成员和函数.使用方法和友元函数一模一样,不再介绍.


内部类

概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元.

特性:

  1. 内部类可以定义在外部类的public、protected、private都是可以的。
  2. 注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
  3. sizeof(外部类)=外部类,和内部类没有任何关系
class A
{
private:
    static int k;
    int h;
public:
    class B
    {
    public:
        void foo(const A& a)
        {
            cout << k << endl;//
            cout << a.h << endl;//   //想要访问外部类的其他成员,必须通过外部类的对象参数.
        }
    };
};
int A::k = 1;

int main()
{
    A::B b;        //定义内部类,只能通过作用域限制符.
    b.foo(A());
    return 0;
}

以上是关于C++入门篇之类和对象总结的主要内容,如果未能解决你的问题,请参考以下文章

C++入门篇之类和对象上

C++基础——C++面向对象之类对象与继承基础总结(类和对象概念构造函数与析构函数this指针继承)

c++基础篇——类与对象入门(下)

C++入门篇string的模拟实现

C++入门篇string的模拟实现

c++基础篇——类与对象入门(下)