类和对象(下)
补:
-
cout是一个全局属性的ostream类型的对象 -
cin是一个全局属性的istream类型的对象 -
运算符重载里面,如果是双操作数,第一个参数是左操作数,第二个参数是右操作数 -
所以说如果我们想要重载流提取或者流插入,我们就不能重载成成员函数(因为成员函数默认第一个函数是this指针) ostream& Date::operator<<(ostream& out)
{
//....
//我们知道里面的参数是有两个(Date* const this,ostream&out);
}
//我们想调用只能---> d1<<cout / d1.operator<<(cout);
这样实现的重载就很不合理,所以我们要是想让cout在左边我们可以实现成全局函数 ostream& operator<<(ostream& out,const Date& d)
{
cout<<d._year<<"-"<<d._month<<"-"<<d.day<<endl;
return out;
}
//但是这又有一个问题,日期类里面的成员变量为私有或者保护的话是不允许全局函数访问的----->c++引入了友元函数
//也就是-->在类里面声明:friend ostream& operator<<(ostream& out,const Date& d);
一、再谈构造函数
之前是在函数体内初始化,这次换一个新的方法
1.初始化列表初始化
//首先看一下语法格式,还是以日期类为例
Date(int year,int month,int day)
:_year(year) //以分号开头,逗号分隔,每一个成员变量在初始化列表中只能初始化一次,不能重复出现,可以不出现
,_month(month) //用成员变量(形参) 用法跟之前写的用法还是一样的
,_day(day)
{
}
总结:
-
可以理解为成员变量定义的地方,所以有了初始化列表就可以初始化类里面的一些const类型的成员变量 //举个例子
class Date
{
public:
Date(int year=1,int month=1,int day=1);
private:
int _year; //这些都是成员变量的声明,并不是定义
int _month; //只有定义才能够给变量赋值,因为只有定义才会给变量开辟空间
int _day;
const int _S;//我们知道这种const修饰的变量在定义的时候必须初始化(我们就规定要给S初始化成30)
};
//1.普通的构造函数
Date::Date(int year,int month,int day)//基于第二种我们还可以这样写
// :_S(30) 3. 我们可以只在定义的时候初始化const修饰的变量,其他变量依然可以在函数内部初始化
{
_year = year;
_month = month;
_day = day;//假如说类实例化了一个对象叫d1,它需要调用构造函数来初始化成员变量,前三个是没有问题的
_S=30; //这就会报错,因为这些成员变量已经被定义出来了,而const这个变量并没有被初始化
}
//2.带有初始化参数列表的构造函数
Date::Date(int year,int month,int day)
:_year(year)
,_month(month)
,_day(day) //这里就可以正常运行,初始化参数列表可以理解为,就是按照顺序去定义成员变量
,_S(30) //而这里就是正好在定义的时候给变量_S初始化了
{
}
-
这么理解需要在定义的时候就初始化的都必须用初始化列表(例如:const修饰的,引用,没有默认构造函数的自定义成员变量) -
内置类型的成员,在函数体和在初始化列表初始化都可以的;自定义类型的成员,建议在初始化列表初始化,这样更高效(可以理解不写初始化列表也会有一个默认的初始化列表,这个列表对内置类型就是初始化一个随机值,对于自定义类型会去调用它的构造函数,会导致多调用一次构造函数) -
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
2.explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用,用explicit修饰构造函数,将会禁止单参构造函数的隐式转换
int main()
{
//假如日期类成员变量只有年份
//虽然下面两个代码都是直接构造,但是过程不一样
//1.
Date d1(2022);
//2.
Date d2=2022; //这也是可以实现构造的--->隐式类型转换
//本来用2022构造一个临时对象Date(2022),再用这个临时对象去拷贝构造d2
//但是由于C++编译器在连续的一个过程中,多个构造会被优化,合二为一
//所以这里被优化为直接就是一个构造
}
如果不想发生隐式类型转换就可以加上explicit关键字
class Date{
public:
explicit Date(int year)
:_year(year)
{
}
private:
int _year;
};
二、static成员
1.概念
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化
class A
{
public:
A(int a=0)
:_a(a)
{
++_count;
}
A(const A& a)
:_a(a._a)
{
++_count;
}
//静态成员函数没有this指针,只能够访问static修饰的成员变量或者成员函数
static int Getcount()
{
return _count;
}
private:
//静态成员变量必须在定义的时候被初始化
//成员变量不储存在实例化的类里面,它是属于整个类,所有成员,生命周期在整个程序的运行期间
//类中的成员函数可以随意访问
int _a;
static int _count;
};
//静态成员在类外初始化,不用写static,但是需要指定类域
int A::_count=0;
int main()
{
A a1(10);
A a2(2);
cout<<A::Getcount()<<endl;//方法1
cout<<a1.Getcount()<<endl;//方法2
}
2.特性
-
静态成员为所有类对象所共享,不属于某个具体的实例 -
静态成员变量必须在类外定义,定义时不添加static关键字 -
类静态成员即可用类名::静态成员或者对象.静态成员来访问 -
静态成员函数没有隐藏的this指针,不能访问任何非静态成员 -
静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值
三、C++11 的成员初始化新方法
//C++ 98版本构造函数不是很好,就是它对自定义类型会去调用自定义类型的构造函数,对于内置类型不处理
//又因为代码需要向前兼容,所以C++11采用了打补丁的方式,来进行了一些优化
class B
{
public:
B(int b = 0)
:_b(b)
{}
private:
int _b;
};
class A
{
public:
//如果在初始化列表阶段没有对成员函数初始化,它就会使用缺省值初始化
A()
{}
private:
int _a=0;//要注意的是这里不是初始化,因为这里是声明,不能初始化
//这里是给成员变量缺省值
B _b1=5;
B _b2=B(10);
int* p=(int*)malloc(4*10);
int arr[10]={1,2,3,4,5};
//这些在C++11中都是支持的,都是在给缺省值,静态成员不能这样给缺省值,必须在类外面全局位置定义初始化
};
四、友元
友元分为:友元函数和友元类
- 友元提供了一种突破封装的方式,有时提供了便利,但是友元会增加耦合度,破坏了封装,所以能不用尽量不用
友元函数
- 友元函数可以访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类的非公有成员
//具体用法就是:如果A类需要去访问B类的非公有成员,那就在B类中声明
friend class A;//就可以了
五、内部类
概念
如果一个类B定义在另一个类A的内部,这个类B就叫做内部类。注意这个内部类B是一个独立的类,它不属于外部类A,更不能通过外部类A的对象去调用内部类B。外部类A对内部类B没有任何优越的访问权限,内部类只受外部类的类域的限制
class A
{
private:
int h;
static int i;
public:
//内部类
//内部类B和在全局定义基本是一致的,只是受外部类A的类域限制
//内部类B天生就是外部类A的友元
class B
{
public:
void fun(const A& a)
{
cout<<a.h<<endl;
cout<<i<<endl;
}
private:
int _b;
};
};
int main()
{
//如果想要使用类B
A::B b1;
}
注意:
内部类天生就是外部类的友元类,注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员,但是外部类不是内部类的友元。
特性
-
内部类可以定义在外部类的public、protected、private都是可以的。 -
注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。 -
sizeof(外部类)=外部类,和内部类没有任何关系
|