类和对象(下)

简介: 类和对象(下)

1. 初始化列表

1. 概念

  • 以一个冒号开始,接着是一个以逗号分隔的1数据成员列表,每个成员变量后面跟一个放在括号的初始值或表达式,即成员变量定义的地方

2. 用法

#include<iostream>
using namespace std;
class date
{
public:
private:
    int _year;//声明
    int _month;
    int _day;
 //const int x;//存在会报错
};
int main()
{
date d(2023, 2, 10);//定义
//const int a=0;
    return 0;
}

const修饰的变量必须在定义时初始化,默认构造函数对内置类型不处理,对自定义类型调用它的默认构造函数,const int为内置类型,不会处理

正常来说,在date类实例化生成对象d时,是对象整体的定义,即对每个成员变量定义

但是像const修饰的变量必须在定义时初始化怎么办?

所以就有了初始化列表

那个对象调用构造函数,初始化列表是它所有成员变量定义的位置

不管是否显示在初始化列表写,编译器每个变量都会在初始化列表定义初始化

成员变量出现随机值

#include<iostream>
using namespace std;
class date
{
public:
    date()
        :a(1)//使用初始化列表进行定义
        ,c(2)  
    {
    }
private:
    int a=2;//声明 
    int b=1;
    int q;
};
int main()
{
    date d;
    return 0;
}

可以在类中的成员变量声明处加入缺省值,若该变量在初始化列表中被定义,则该变量为被定义后的值,若在初始化列表中没有被定义,则该变量输出缺省值

a=1,b=1,q=随机值

不管是否在初始化列表中写入全部的成员变量,都会全部调用一次

例如 a,b,两者都有缺省值,在初始化列表中a被定义为1,则输出a=1,

b在初始化列表没有被定义,所以输出缺省值,b=1

q作为内置类型int,没有缺省值,也没有初始化列表定义,所以q为随机值


3.注意事项

1.每个成员变量只能在初始化列表出现一次(初始化只能初始化一次)

2.类中包含 引用成员变量 const成员变量 自定义类型成员(没有默认构造函数) ,必须放在初始化列表位置进行初始化,(剩下的成员在初始化列表写不写都行,但是上述这三类就必须在列表写)

#include<iostream>
using namespace std;
class A
{
public:
    //A() //不传参数为默认构造函数
    //    :_a(1)//如果自定义类型的构造函数为这个 就不用在初始化列表初始化
    //{
    //}
    A(int d)
        :_a(d)
    {
    }
private:
    int _a;
};
class date
{
public:
    date()
        :a(2)
        ,b(1)
        ,c(a)
        ,aa(5)
    {
    }
private:
    int a = 1;//内置类型int
    const int b = 2;//const
    int& c;//引用
    A aa;//自定义类型并且没有默认构造函数
};
int main()
{
//a=2 b=1 c=2 aa._a=5
    date d;
    return 0;
}
  • 因为构造函数对于内置类型不处理,对于自定义类型调用它的默认构造函数,
    所以定义时必须初始化的包含自定义类型(不带默认构造函数)

3.成员变量在类中的声明次序就是在初始化列表中的初始化顺序,在初始化列表中的先后次序无关

例题

class A
{
public:
    A(int a)
        :_a1(a) //1
        , _a2(_a1)
//随机值
    {}
    void Print() {
        cout << _a1 << " " << _a2 << endl;
    }
private:
    int _a2;
    int _a1;
};
int main() {
    A aa(1);
    aa.Print();// 1随机值
}

由于是先声明 _a2,再声明_a1,所以在初始化列表中先调用_a2,由于刚开始_a1为随机值 ,所以_a2为随机值,再调用_a1,将a的值1传给_a1,所以_a1=1,最终是 1 随机值

2.explicit关键字

1.单参数的构造函数 (C++98) ,支持类型转换

#include<iostream>
using namespace std;
class A
{
public:
    A(int n)//构造函数
        :_a(n)
    {
        cout << "A(int n)" << endl;
    }
    A(A& d)//拷贝构造
    {
        cout << "A(A&d)" << endl;
    }
private:
    int _a;
};
int main()
{
    A a(1);//构造函数     构造
    A b = 1;//隐式类型转换 构造+拷贝构造->优化  构造
    //int i = 1;
    //double d = i;//隐式类型转换
    return 0;
}

是int对于double的类型转换,产生一个类型为double的临时变量,再将临时变量传给d

内置类型int对于自定义类型A的类型转换,通过构造产生一个类型为A的临时变量,再通过拷贝构造传给b

但是程序运行发现,只进行了两次构造,并没有拷贝构造,

说明C++对于自定义类型产生临时变量,编译器会做优化

1. 验证临时变量的存在

#include<iostream>
using namespace std;
class A
{
public:
    A(int n)
        :_a(n)
    {
        cout << "A(int n)" << endl;
    }
    A(A& d)
    {
        cout << "A(A&d)" << endl;
    }
private:
    int _a;
};
int main()
{    
    //A& ret = 1;//错误
    const A& ret = 1;
    return 0;
}
  • A&ret=1会报错,而const A&ret=1却可以
  • 因为通过构造生成一个类型为A的临时变量,而临时变量具有常性,ret为临时变量的别名, 将临时变量传过去,由const A类型到A类型会造成权限放大,所以要加const修饰ret,说明临时变量的存在

2.关键字explicit的使用

#include<iostream>
using namespace std;
class A
{
public:
    explicit A(int n)//构造
        :_a(n)
    {
        cout << "A(int n)" << endl;
    }
    A(A& d)//拷贝构造
    {
        cout << "A(A&d)" << endl;
    }
private:
    int _a;
};
int main()
{
    A a(1);//构造函数
    A b = 1;//隐式类型转换 加入关键字explicit会报错
    return 0;
}
  • A&ret=1会报错,而const A&ret=1却可以
  • 因为通过构造生成一个类型为A的临时变量,而临时变量具有常性,ret为临时变量的别名, 将临时变量传过去,由const A类型到A类型会造成权限放大,所以要加const修饰ret,说明临时变量的存在

2.关键字explicit的使用

#include<iostream>
using namespace std;
class A
{
public:
    explicit A(int n)//构造
        :_a(n)
    {
        cout << "A(int n)" << endl;
    }
    A(A& d)//拷贝构造
    {
        cout << "A(A&d)" << endl;
    }
private:
    int _a;
};
int main()
{
    A a(1);//构造函数
    A b = 1;//隐式类型转换 加入关键字explicit会报错
    return 0;
}
  • 为了防止隐式类型转换发生,所以在构造函数前加入关键字explicit

2.多参数构造函数(C++11) 使用{ }支持类型转换换

#include<iostream>
using namespace std;
class A
{
public:
    explicit A(int n,int a)
        :_a(n)
                ,_b(a)
    {
        cout << "A(int n,int a)" << endl;
    }
    A(A& d)
    {
        cout << "A(A&d)" << endl;
    }
private:
    int _a;
 int _b;
};
int main()
{
    //多参数的构造函数
    A a(1, 2);
    A a = { 1,2 };//隐式类型转换 加入关键字explicit就会报错
    return 0;
}

同样在多参数的构造函数中,使用关键字explicit也可以防止隐式类型转换的发生

3.友元

1.友元函数

1.概念

为了在类外面使用类中私有的成员变量,友元提供了突破封装的方式,在类中加入 friend+函数定义

但是这样会增加耦合度,所以不建议多用

2.实现cout功能

#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 << _year << "-" << _month <<"-"<< _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    date d1(2023, 2, 10);
    //cout << d1; 我们想实现成的
    d1 << cout; //实际在类中生成的
    return 0;
}

在类中,左操作数作为隐藏的this指针,指针类型为date*,

cout<

cout是标准库std命名里面一个ostream类型的全局对象,与this指针类型不符,所以在类中只能写成 d1<

虽然输出了日期,但是输出方式不是我们想要的结果,我们想要使用 cout<

#include<iostream>
using namespace std;
class date
{
public:
    friend void operator<<(ostream& out, date& d);//firend+函数定义 说明函数为类的友元函数
    date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
private:
    int _year;
    int _month;
    int _day;
};
void operator<<(ostream& out, date&d)
{
    out << d._year << "-" << d._month << "-" << d._day << endl;
}
int main()
{
    date d1(2023, 2, 10);
    cout << d1;
    return 0;
}

所以设置在类外面,这样就会把两个操作数都传过来当参数,没有this指针的问题,

同时 在类中 friend+函数定义,说明该函数是date类的友元函数,该函数不受访问限定符的限制

3.实现cin的功能

#include<iostream>
using namespace std;
class date
{
public:
    friend void operator>>(istream& in, date& d);
private:
    int _year;
    int _month;
    int _day;
};
void operator>>(istream& in, date& d)
{
    in >> d._year >> d._month >> d._day;
}
int main()
{
    date d1;
    cin >> d1;
    return 0;
}

cin是标准库std命名里面一个istream类型的全局对象,

在类外面定义需要在date类中加入friend+函数定义,使函数成为类的友元函数,该函数不受访问限定符的限制


4.说明

1.友元函数可以访问类的私有和保护成员,但是不是类的成员函数

2.友元函数不能用const修饰

3.友元函数可以在类的定义的任何地方声明,不受访问限定符限制

2.友元类

class Time
{
public:
    friend class date;//日期类是时间类的友元
    Time(int hours=0,int minute=0,int second=0)
    {
        _hour = hours;
        _minute = minute;
        _second = second;
    }
private:
    int _hour;
    int _minute;
    int _second;
};
class date
{
public:
    date(int year=1, int month=1, int day=1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void print(int hour,int minute,int second)
    {
        //直接调用时间类的私有成员变量
        _t._hour = hour;
        _t._minute = minute;
        _t._second = second;
    }
private:
    int _year;
    int _month;
    int _day;
    Time _t;
};

由于日期类是时间类的友元,相当于日期类是时间类的好朋友,所以在日期类中使用时间类的私有成员变量,

但是反之,时间类并不是日期类的好朋友,也就不能在时间类中调用日期类的私有成员变量


说明

1.友元是单向的,不具有交换性

2.友元不能传递,如果C是B的友元,B是A的友元,不能说明C是A的友元


4.static 成员

1.概念

用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化(后面会解释)

2.实现一个类,计算类中创建了多少个对象

#include<iostream>
using namespace std;
class A
{
public:
    A(int a=0)
    {
        n++;
    }
    A(A& d)
    {
        n++;
    }
    static int getn()//静态成员函数没有this指针
    {
        return n;
    }
private:
    //不属于某个对象,属于所有对象,属于整个类
    static int n;//静态成员变量声明
};
int A::n = 0;//静态成员变量定义
int main()
{
    A a;//构造
    A b;//构造
    A c(a);//拷贝构造
    A d(b);//拷贝构造
    cout << A::getn() << endl;//4
    return 0;
}

静态成员变量声明时可以有缺省值吗?

不可以,缺省值是在初始化列表处进行初始化,而初始化列表是初始化非静态成员(属于对象的成员),而static修饰的成员属于共有的


3. 特性

1.静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区

static修饰的成员变量 不属于某个对象,属于所有对象,属于整个类


2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明

静态成员变量属于共有的,不能在初始化列表初始化,所以只能在类外初始化


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


4.静态成员也是类的成员,受public、protected、private 访问限定符的限制

若在类外直接使用,对象调用处于类中私有的成员变量

5. 匿名对象

#include<iostream>
using namespace std;
class date
{
public:
    date()
    {
        cout << "date()" << endl;
    }
    ~date()
    {
        cout << "~date()" << endl;
    }
    void print()
    {
        cout << "print" << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    date();//匿名对象,生命周期只在第一行
    date().print();
    return 0;
}

匿名对象特点为不用取名字,由于它的生命周期只有这一行,在下一行会自动调用析构函数

同时可以不用创建对象来调用类中的函数,直接使用匿名对象.函数

匿名对象的返回

#include<iostream>
using namespace std;
class date
{
public:
    void print()
    {
        cout << "print" << endl;
    }
    int  sum(int n)
    {
        return n;
    }
private:
    int _year;
    int _month;
    int _day;
};
class A
{ 
public:
    A(int n)
    {
    }
};
A fun()
{
    int n = 0;
    cin >> n;
    int ret = date().sum(n);
     //A d=ret; //正常来说创建一个对象接收 ,在返回对象
    //return d;
    return A(ret);//直接返回匿名对象
}
int main()
{
    date();//匿名对象,生命周期只在第一行
    return 0;
}
  • 正常来说,需要创建一个A类型对象通过隐式类型转换接收ret,在返回对象 或者直接返回匿名对象 A(ret)

6.内部类

  • 如果在一个类定义在另一个类的内部,这个类就叫做内部类
#include<iostream>
using namespace std;
class A
{
private:
    int n;
public:
    class B
    {
    public:
        void print()
        {
        }
    private:
        int b;
    };
};
int main()
{
    cout << sizeof(A) << endl;//4
    A::B b;//必须通过A类,然后才能访问B类创建对象b
    return 0;
}

当我们计算A类的大小时发现为4,说明只计算了A类自身私有的成员变量n,并没有算上内部类B的成员变量

说明内部类B跟A是独立的,受A的类域限制

内部类是外部类的友元

class A
{
public:
    class B//B是A的友元
    {
    public:
        void print(A&d)
        {
            //在B类中可以调用A的私有成员变量
            cout << d.n << endl;
        }
    private:
        int b;
    };
private:
    int n;
};
int main()
{
    cout << sizeof(A) << endl;//4
    return 0;
}
  • 可以在内部类B中调用外部类A的私有成员变量n

7.拷贝对象时的一些编译器优化

class A
{
public:
    A(int a = 0)
        :_a(a)
    {
        cout << "A(int a)" << endl;
    }
private:
    int _a;
};
int main()
{
    A aa=1;//构造函数+拷贝构造 优化为 构造
    return 0;
}

单参数的构造函数,编译器把构造函数+拷贝构造优化为构造(上面的explicit关键字做出详细解释)

1.传值传参

class A
{
public:
    A(int a = 0)
        :_a(a)
    {
        cout << "A(int a)" << endl;
    }
private:
    int _a;
};
void func(A a)
{
}
int main()
{
    func(2);//构造+拷贝构造 优化-> 构造
    return 0;
}
  • 这里的传值传参func(2)可以看作是在一个表达式中,将内置类型int的2传给 自定义类型A的a,发生隐式类型转换
  • 编译器 把构造函数+拷贝构造 优化为构造

2.引用传参

class Aclass A
{
public:
    A(int a = 0)
        :_a(a)
    {
        cout << "A(int a)" << endl;
    }
private:
    int _a;
};
void func2(const A& a)//必须使用const修饰,临时变量具有常性
{
}
int main()
{
    func2(2);//无优化
    return 0;
}
  • A&a=2 这样写会报错,而加上const 修饰变成 const A&a=2就通过了
    说明存在临时变量,而临时变量具有常性,a作为临时变量的别名,要加上const保持权限一致
  • 无优化,必须存在临时变量

3.传值返回

class A
{
public:
    A(int a = 0)
        :_a(a)
    {
        cout << "A(int a)" << endl;
    }
    A(const A& aa)
        :_a(aa._a)
    {
        cout << "A(const A& aa)" << endl;
    }
private:
    int _a;
};
A func3()
{
    A a;//构造
    return a;
//拷贝构造
}
int main()
{
    func3();//构造+拷贝构造 无优化
    return 0;
}

return a返回时,因为传值返回,所以会拷贝构造一个临时变量

不会优化,因为 A a与 return a不在一个表达式中

#include<iostream>
using namespace std;
class A
{
public:
    A(int a = 0)
        :_a(a)
    {
        cout << "A(int a)" << endl;
    }
    A(const A& aa)
        :_a(aa._a)
    {
        cout << "A(const A& aa)" << endl;
    }
private:
    int _a;
};
A func3()
{
    A a;
    return a;
}
int main()
{
    A b=func3();
    return 0;
}

  • retun a返回通过拷贝构造生成一个临时变量,再把临时比变量拷贝构传给b 正常来说是进行 一次构造、两次拷贝构造
  • 两次拷贝构造属于一个连续的步骤,所以编译器进行了优化,

最终只进行了 一次构造、一次拷贝构造

4.匿名对象的传值返回

class A
{
public:
    A(int a = 0)
        :_a(a)
    {
        cout << "A(int a)" << endl;
    }
    A(const A& aa)
        :_a(aa._a)
    {
        cout << "A(const A& aa)" << endl;
    }
private:
    int _a;
};
A func4()
{
    return A();
}
int main()
{
    A b=func4();//构造+拷贝构造+拷贝构造  优化成 构造
    return 0;
}


因为整体都在一个表达式中,所以在构造匿名对象,拷贝构造生成一个临时变量,在用临时变量拷贝构造传给b这个过程中都会被编译器优化,构造+拷贝构造+拷贝构造 优化成 构造

5.两种习惯之间的差异

class A
{
public:
    A(int a = 0)
        :_a(a)
    {
        cout << "A(int a)" << endl;
    }
    A(const A& aa)
        :_a(aa._a)
    {
        cout << "A(const A& aa)" << endl;
    }
    A& operator=(const A& aa)
    {
        cout << "A& operator=(const A& aa)" << endl;
        if (this != &aa)
        {
            _a = aa._a;
        }
        return *this;
    }
private:
    int _a;
};
A func4()
{
    A a;
    return a;
}
int main()
{
    A c = func4();//构造+拷贝构造+拷贝构造 优化->构造+拷贝构造
    cout << "---------------" << endl;
    A b;//构造
 b = func4();//构造+拷贝构造+赋值  无优化
    return 0;
}

横线上面直接用一个表达式调用函数,调用函数,A a 构造一次,return a,拷贝构造生成一个临时变量,再用临时变量拷贝构造b,由于 两次拷贝构造是连续步骤,所以优化成一次拷贝构造

即 构造+拷贝构造

而横线下面先是在主函数中 A b构造一次 ,调用func4函数, A a构造一次 ,return a拷贝构造生成一个临时变量,由于此时的b已经被定义雇过了,所以此时 属于将临时变量赋值给 b ,进行运算符重载

所以 无优化 即 构造+构造+拷贝构造+赋值



相关文章
|
8月前
|
存储 程序员 数据安全/隐私保护
C++ 类和对象(一)
C++ 类和对象(一)
30 0
|
1月前
|
存储 Java 数据安全/隐私保护
类和对象是什么?(上)
类和对象是什么?
32 4
|
25天前
|
Java 编译器 C++
4. C++类和对象(下)
4. C++类和对象(下)
|
1月前
|
存储 编译器 C语言
【C++】类和对象(上)
【C++】类和对象(上)
|
1月前
|
编译器
C++-类和对象(3)
C++-类和对象(3)
28 0
|
10月前
|
存储 编译器 C语言
【类和对象(上)】(二)
【类和对象(上)】(二)
26 0
|
6月前
|
Java 编译器
类和对象!
类和对象!
33 1
|
6月前
|
存储 编译器 C语言
类和对象(上)
类和对象(上)
52 0
|
6月前
|
Java 编译器
类和对象(一)
类和对象(一)
52 0