类和对象

时间:2023-02-13 11:35:50

@​​TOC​

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;
}
  • 为了防止隐式类型转换发生,所以在构造函数前加入关键字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.概念

为了在类外面使用类中私有的成员变量,友元提供了突破封装的方式,<font color=red>在类中加入 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<<d1等价于cout.operator<<(d1),若为cout<<d1,就会将cout作为左操作数,
cout是标准库std命名里面一个ostream类型的全局对象,与this指针类型不符,所以在类中只能写成 d1<<cout 虽然输出了日期,但是输出方式不是我们想要的结果,我们想要使用 cout<<d1

#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;
}

<font color=blue>cin是标准库std命名里面一个istream类型的全局对象,
在类外面定义需要在date类中加入friend+函数定义,使函数成为类的友元函数,该函数不受访问限定符的限制</font>

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指针,不能访问任何非静态成员<font color=red>4.静态成员也是类的成员,受public、protected、privae 访问限定符的限制
    若在类外直接使用,对象调用处于类中私有的成员变量

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 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 ,进行运算符重载 所以 <font color=red>无优化 即 构造+构造+拷贝构造+赋值