目录
面向过程和面向对象初步认识
类的引入
类的定义
类的访问限定符及封装
类的作用域
类的实例化
类的对象大小的计算
*类成员函数的this指针
*类的6个默认成员函数
构造函数
析构函数
拷贝构造函数
赋值操作符重载
* 默认拷贝构造与赋值运算符重载的问题
const成员函数
取地址及const取地址操作符重载
*再谈构造函数
C++11 的成员初始化新玩法
*友元
static成员
内部类
再次理解封装
C语言是面向过程的,关注的是过程,将问题分解成具体的步骤;
C++是面向对象 的,关注的是对象,将一件事拆分成不同的对象
C++中类的关键字有 class 和 struct
我们知道在C语言中的struct是结构体的关键字 可以用来定义结构体,结构体里面可以定义基本类型的变量;但是在C++中struct不只是结构体的关键字还是一个类的关键字,它兼容了C语言中struct的所有用法的同时又上升了一个等级,变成了高大上的类。
例如:
//f1是一个类的类名 这个类包括成员变量_a _b _c和成员函数fuc1
struct f1
{
int _a;//成员函数的声明
int _b;
int _c;
int fuc1(int a, int b)//成员函数的定义
{
return a + b;
}
};
这里的struct是一个类关键字 可以用来定义一个类 但是C++中更喜欢用class来定义一个类
这是为什么呢?
因为struct修饰的类的成员的默认访问权限是public而用class修饰的类的成员的默认访问权限是private 这就是二者的区别
那么class有啥优势呢 对与一个对象来说C++会对对象进行封装 一般不会让类外的东西直接改变类中的数据 可以通过类中的函数来改变类中的数据,封装提高了类的安全性。
通过封装的特性,我们知道一个类要尽可能的提高其安全性,struct类的默认范问权限是共有的,在类外就可以改变类里面的数据,这是十分不安全的,相反class类的默认访问权限是私有的,只能通过类内的函数等改变类内的数据,类外面是没有权限访问和改变类里面的数据的,安全性很高。这就是为什么更喜欢用class来定义类的原因!
基本格式:
//class 类名
//{
// //类体 包括成员变量和成员函数
//};//要加分号
class person
{
public:
void print()
{
cout << age << name << weigh << endl;
}
private:
int age;
char* name;
double weigh;
}
C++中的类访问限定符有public private protect 三种
public:代表公有 其修饰的成员可以在类的外面直接被访问
private: 代表私有 其修饰的类在类外不能直接被访问
protect:代表保护 与private是相似的
拓展小知识-> 访问限定符只有在编译的时候有用,当数据映射到内存后,没有任何访问限定符上的区别
封装
封装:就是将数据和操作数据的方法进行有机的结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行 交互。 封装本质上是一种管理 不想给别人看到的,就使用protected/private把成员封装起来。然后开放一些共有的成员函数对成员合理的访 问。所以封装本质是一种管理。
只能看到外在美,看不到它的内在美!!!
一个类的非静态成员变量的作用域就只存在于这个类中 出了这个类就出了作用域 (即花括号里面才有用)如果我们要在类外定义成员就要用::(域作用限定符)来指定成员属于哪个类域
例如:
class information
{
public:
void setinfo(char* name, int age, int Class, char* adress)
{
_name = name;
_age = age;
_Class = Class;
_adress = adress;
}
void printinfo();//给了声明没有定义 在类外定义
private:
char* _name;
int _age;
int _Class;
char* _adress;
};
void information::printinfo()//用::来指定类域 说明是定义哪个类里面的函数
{
cout << _name << _age << _Class << _adress << endl;
}
用类类型创建对象的过程,称为类的实例化
1. 类只是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它
2. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
3. 做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什 么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占 用物理空间
例如我们定义了一个信息类
class information
{
public:
void setinfo(string name, int age, int Class,const string adress)
{
_name = name;
_age = age;
_Class = Class;
_adress = adress;
}
void printinfo()
{
cout << _name <<" "<< _age <<" "<< _Class << " "<<_adress << endl;
}
private:
string _name;
int _age;
int _Class;
string _adress;
};
int main()
{
information person1;
person1.setinfo("zhangsan", 20, 2, "jiangxi");
person1.printinfo();
return 0;
}
在主函数中用information 类实例化了一个person1对象 再调用person1的成员函数对自己的成员变量赋值,再调用打印函数打印出赋值后的结果
我们知道一个类 包含成员变量和成员函数 那么一个类的大小是否是成员变量和成员函数的大小的和呢?
首先来思考一个问题 我们一个类可以实例化多个对象 那么每个对象里的成员变量大部分是各不相同的 但是成员函数是一模一样的 所以如果我们实例化了多个对象 假如类的对象大小是成员函数和成员变量的和 那么就会有非常多的成员函数 它们都是一样的却有很多份 是不是会很浪费空间呢?
答案是 会浪费很多空间
所以我们最初的猜想是不成立的 类的对象的大小只是成员变量的大小的和 不包括成员函数的大小 因为c++中成员函数是存放在公共代码区的 不是在对象里的 只有成员变量是存放在对象里的
注意:一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐,注意空类的大小,空类比 较特殊,编译器给了空类一个字节来唯一标识这个类。
结构体内存对齐规则
1. 第一个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的对齐数为8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是 所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
我们先来看一个例子 抛出问题
class Date
{
public:
void SetDate(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
void ShowDate()
{
cout << _year << " " << _month << " " << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.SetDate(2000, 2, 4);//实例化出一个日期类 并且设置好日期
d1.ShowDate();
Date d2;
d2.SetDate(2008, 3, 4);
d2.ShowDate();
}
上面我们定义了一个日期类 然后实例化了俩个对象 d1 d2对然后通过d1.setdate和d2.setdate对它们的成员变量赋值 然后打印出来d1和d2确实是我们设置的那样 但是问题是d2和d2 都是调的setdate这个函数 那么setdate这个函数是怎么准确地找到自己是对谁进行设置呢?
这就是this指针的功劳了
在c++中调用一个对象的函数的时候,编译器都会隐藏一个指向所操作的对象的this指针,这个this指针成员函数的一个参数,只不过被隐藏起来了,我们看不到,但是我们可以在成员函数中使用this指针。
有了this指针那么函数就可以通过this指针准确地找到自己要操作的对象!!! 见图
//如果成员变量名与形参名相同 可以用this->成员变量=形参 的形式解决
void SetDate(int year,int month,int day)
{
this->year = year;
this->month = month;
this->day = day;
}
this指针的特性
1. this指针的类型:类类型* this
2. 只能在“成员函数”的内部使用
3. this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this 形参。所以对象中不存储this指针。
4. this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户 传递
5.this指针是形参是存在栈上的
饭后甜点——>请听题
解析:这里考察的是this指针的传递,我们在调用成员函数的时候编译器会隐藏一个this指针参数,所以我们调用PrintA和Show函数的时候,其实都有有一个this指针在函数的参数里;
当我们调用PrinitA的时候PrintA会通过传过来的this指针去找成员变量_a,就是cout<
那如果把p->PrintA();去掉,就只剩p->Show();了 很明显Show没有对this指针(空指针)进行任何操作 所以不会报错
如果我们构造造了一个类,没有成员变量,没有成员函数,那么这就是一个空类,但是空类并不是什么都没有的,空类会有默认的六个成员函数,这六个默认的成员函数是我们不写编译器自动生成的。
上面的日期类中为了设置成员变量,在类里面写了一个setdate的函数来完成这个功能。那么有没有一直方法不用去调用自己写设置成员变量的函数,让它自己完成赋值初始化的操作呢?
当然有咯!它就是构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员 都有 一个合适的初始值,并且在对象的生命周期内只调用一次。
构造函数有以下特征:
num.1构造函数的函数名和类名相同
num.2构造函数没有返回值
num.3对象实例化时编译器自动调用对应的构造函数
num.4构造函数是可以重载的
以下是class类的一些构造函数的形式:
class Date
{
public:
Date()//无参的构造函数
{
}
Date(int year, int month, int day)//带参数的构造函数
{
_year = year;
_month = month;
_day = day;
}
//Date(int year = 0, int month = 0,int day = 0)//带全缺省参数的构造函数
//{
// _year = year;
// _month = month;
// _day = day;
//}
void ShowDate()
{
cout << _year << " " << _month << " " << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; // 调用无参构造函数
Date d2 (2015, 1, 1); // 调用带参的构造函数
return 0;
}
值得注意的是:因为构造函数是可以重载的,所以上面的无参的构造函数和带全缺省参数的构造函数如果同时出现在类中是可能出现矛盾的,比如调用无参的构造函数的时候就会出问题 ,这两种构造函数都可以不带参数,因为无参的可以调用无参的构造函数,也可以认为是缺省了会调带全缺省参数的构造函数,两个都符合,那么编译器就不知道到底用哪个,就会报错哦!
如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定 义编译器将不再生成
class Date
{
public:
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;//没有写构造函数 编译器会自动生成 这样写也是没有问题的
return 0;
}
注意:无参构造函数,有参构造函数,带缺省的构造函数,编译器自动生成的构造函数都可以当成构造函数,但是默认的构造函数只能有一个,也就是说只能是上面四个中的一个,不能是多个!
下面来看一个问题,编译器默认生成的构造函数会不会对成员变量初始化呢?
这就是答案:
很显然,编译器自动生成的构造函数并没有对成员变量进行初始化,成员变量依旧是随机值!!
那么问题来了,既然编译器自动生成的构造函数不会对成员变量进行初始化,那我们调用构造函数有鸟用?(这个缺陷在c++11中有优化,后面会提到)
其实还是有点鸟用的
C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如 int/char...,对于内置类型是不会进行初始化的,自定义类型就是我们使用class/struct/union自己定义的类型,编译器生成默认的构造函数会对自定类型成员调用的它自己的默认构造函数
例如:
class S
{
public:
S(int x=9)//自己带缺省值的默认构造函数
{
_x = x;
}
int getx()//返回成员变量_x的值
{
return _x;
}
private:
int _x;
};
class Date
{
public:
void ShowDate()
{
cout << _year << " " << _month << " " << _day <<" _s的成员变量的值是:" << _s.getx() << endl;
}
private:
int _year;
int _month;
int _day;//内置类型的成员变量
S _s;//自定义类型的成员变量 有自己默认的构造函数
};
int main()
{
Date d1;
d1.ShowDate();//看看编译器默认生成的构造函数会不会对成员变量赋初值
}
通过运行结果可以看出 对于内置类型的成员变量,编译器默认生成的构造函数不会对其进行初始化赋值,它们的值是随机值;但是对于自定义类型的成员变量,编译器会去调用自定义类型它自己本身的默认的构造函数,这里的S 是有默认的构造函数的,所以在没有写构造函数的情况下,Date会有一个编译器自动生成的构造函数,会去调用S本身的默认构造函数去初始化这个自定义的类型
什么是析构函数?
析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而 对象在销毁时会自动调用析构函数,完成类的一些资源清理工作
析构函数是特殊的成员函数。 具有以下四个特征:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值。
3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
class A
{
public:
A(int a)
{
_a = a;
}
~A()//调用了析构函数就会打印 ~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
A a(2);//创建了两个类 a a1
A a1(8);
return 0;
}//是在程序结束的时候才去调用析构函数的
注意:这两个~A是在程序快结束的时候打印的
析构函数也是会区分内置类型和自定义类型的,编译器默认生成的析构函数会对内置类型直接清理,但是对于自定义类型,就会去调用自定义类型自己默认的析构函数;
例如: string 是一个自定义类型,在person类中没有写析构函数,编译器会默认生成一个析构函数,对于string这个自定义类型,会调用string自己默认的析构函数(自己写了那么自己写的就是默认的析构函数,没有写就是编译器自动生成的析构函数为默认析构函数)那么就会打印~string这个字符串了
构造函数和析构函数的调用是后进先出的(LIFO)
例如:
#include
using namespace std;
class A
{
public:
A(int _a)
{
this->_a=_a;
cout<<"A(int _a)"<_b=_b;
cout<<"B(int _b)"<
思考:在创建对象时,可否创建一个与一个对象一某一样的新对象呢?
可以。用构造函数。
构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象 创建新对象时由编译器自动调用
拷贝构造函数也是特殊的成员函数,其特征如下:
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用
3. 若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷 贝,这种拷贝我们叫做浅拷贝,或者值拷贝
例如:拿一个创建好的对象去拷贝构造另一个新创建的对象
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year =d._year;
_month =d._month;
_day = d._day;
}
void print()
{
cout << _year << " " << _month << " " << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date a(2002, 2, 3);
Date b(a);
//上面一行也可以写成Date b=a; 也算是拷贝构造
a.print();
b.print();
return 0;
}
注意:拷贝构造函数一定得是传引用 不能是传值 否则会无穷递归下去
运算符重载 C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类 型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。 函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意: 不能通过连接其他符号来创建新的操作符:比如operator@ 重载操作符必须有一个类类型或者枚举类型的操作数
用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的 操作符有一个默认的形参this,限定为第一个形参
.* (点星) 、:: 、sizeof 、?: 、. (点)注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
前置++和后置++的区别是 前置加加没有标志位,后置加加有标志位
前置加加:
operator++()
后置加加:
operator++(int)
下面拿日期类的几个运算符重载来举例说明->
1.Date& operator=(const Date& d); 赋值运算符重载
赋值运算符重载的几个点:
1. 参数类型
2. 返回值
3. 检测是否自己给自己赋值
4. 返回*this
5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。
Date& Date::operator=(const Date& d)//这里为了提高效率,用了传引用作为参数 返回值也是引用
{
if(this!=&d)//如果不是自己给自己赋值就进行
{
this->_year=d._year;
this->_month=d._month;
this->_day=d._day;
}
return *this;
}
2.bool operator>(const Date& d);//这里的参数传的是日期类 可以用传引用来提高效率
bool Date::operator>(const Date& d)
{
if (this->_year > d._year)
{
return true;
}
else if (this->_year == d._year)
{
if (this->_month > d._month)
{
return true;
}
else if (this->_month == d._month)
{
if (this->_day > d._day)
{
return true;
}
}
}
return false;
}
拷贝构造是用一个已经创建了(且初始化了的同类的对象)去初始化新创建的对象
而赋值运算符重载是将两个已经创建好的对象进行赋值操作
例如:
Date d1(2003,4,2);//创建了一个对象并且初始化了
Date d2(2005,3,1);//创建了一个对象并且初始化了
Date d3(d1);//这是拷贝构造 是拿已经创建好且初始化了的d1来拷贝构造d3
Date d4=d1;//这也是拷贝构造,只是写法不同 ,也是拿已经创建好的d1来拷贝构造d4
d1=d2;//这是赋值运算符重载 ,是将两个已经创建好的对象进行赋值 把d2赋值给d3
c++中用const 来修饰成员函数是为了修饰成员函数里的this指针,表示在该成员函数中不能对类的任何成员进行修改。
定义了一个const类和非const类 然后都调用无const 修饰的Display函数 结果报错 const 对象的this指针是const Date* 非const类的指针是 Date* 不能把const Date* 传给Date* 这样就是权限放大了,是不可以的,只能是权限缩小。
比如
非const对象和const对象的指针都可以传给const修饰的函数,因为前者是权限缩小,后者是权限不变,只要不是权限放大都是可以的。
小结论:
const对象可以调用非const成员函数吗? 不可以,权限放大了
非const对象可以调用const成员函数吗? 可以,权限缩小
const成员函数内可以调用其它的非const成员函数吗? 不可以,权限放大了
非const成员函数内可以调用其它的const成员函数吗? 可以,权限缩小了
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
//如果不想被别人取到对象的地址,可以把这个默认取地址函数写成下面的样子
//Date* operator&()
//{
//return nullptr ;
//}
//const Date* operator&()const
//{
//return nullptr;
//}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比 如想让别人获取到指定的内容!
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
经过构造函数后,对象里的成员变量会有一个初始值,但是这不能说是对象的初始化,构造 函数体中的语句只能将其称作为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内 可以多次赋值
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括 号中的初始值或表达式。
例如:
Date1(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{
}
注意:
1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
引用成员变量 const成员变量 自定义类型成员(该类没有默认构造函数)
因为引用成员变量和const成员变量定义的时候都需要初始化,否则编译不成功,所以它们必须放在初始化列表进行初始化。
class Date
{
public:
Date(int year,int month,int day,int x)
:_year(year)
,_month(month)
,_day(day)
,a(x)
,b(x)
{
}
private:
int _year;
int _month;
int _day;
const int a;
int& b;
}
对于没有默认构造函数的自定义类型 可以通过初始化列表初始化
class Time
{
public:
//没有缺省值那么下面Date类中的_t就没有了可以匹配的默认的构造函数 可以用初始化列表来初始化
Time(int hour)
:_hour(hour)
{
cout << "Time()" << endl;
}
private:
int _hour;
};
class Date
{
public:
// 自定义类型,使用初始化列表 -》 构造函数
/*Date(int day, int hour)
:_t(hour)
{}*/
private:
int _day;
Time _t;
};
int main()
{
Date d(12, 12);
return 0;
}
将Time 的构造函数加一个缺省值_t就会有默认的构造函数 ,可以初始化列表初始化也可以通过拷贝构造进行初始化
class Time
{
public:
Time(int hour=0)
:_hour(hour)
{
cout << "Time()" << endl;
}
private:
int _hour;
};
class Date
{
public:
// 自定义类型,使用初始化列表 -》 构造函数
/*Date(int day, int hour)
:_t(hour)
{}*/
// 自定义类型,不使用初始化列表 -》 构造函数 + operator=
Date(int day, int hour)
{
// 函数体内初始化
Time t(hour);
_t = t;
}
private:
int _day;
Time _t;//这里是有默认构造函数的 因为有个全缺省的构造函数 不给参数会给缺省值然后调用构造函数
};
int main()
{
Date d(12, 12);
return 0;
}
小结:
1.可以理解成一个对象的单个成员变量在初始化列表是它初始化的阶段。
2.自定义类型最好用初始化列表进行初始化,因为不管是否使用初始化列表初始化,对于自定义类型而言,一定会使用初始化列表进行初始化。
还有一个值得注意的点是:成员变量在类中声明的次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
例如:
在类中_a 是先声明的,_b是后声明的,所以初始化列表会先去初始化_a,此时的_b还没有初始化时随机值 拿_b去初始化_a 所以_a打印出来时随机值,然后初始化列表去初始化_b用a去初始化_b
那么就是1了。
上面提到了c++默认生成的构造函数对内置类型的成员变量是不会进行初始化的,这个问题在c++11中得到了解决。
例如:
class Date
{
public:
Date()
:arr(nullptr)
{
}
void print()
{
cout << _year << " " << _month << " " << _day << " " << arr << " " << brr << endl;
}
private:
//这里是声明 不是定义,int _day=0看起来是定义,其实不是,这里可以当成是给的缺省值,不能当成初始化,只是如果上面的构造函数没有对成员变量
//进行初始化的时候,就会把缺省值赋值给成员变量
int _year = 1;
int _month = 2;
int _day = 3;
int* arr;
int* brr = (int*)malloc(sizeof(int));
};
int main()
{
Date a;
a.print();
return 0;
}
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
,arr(nullptr)
,brr(nullptr)
{
}
void print()
{
cout << _year << " " << _month << " " << _day << " " << arr << " " << brr << endl;
}
private:
//这里是声明 不是定义,int _day=0看起来是定义,其实不是,这里可以当成是给的缺省值,不能当成初始化,只是如果上面的构造函数没有对成员变量
//进行初始化的时候,就会把缺省值赋值给成员变量
int _year = 0;
int _month = 0;
int _day = 0;
int* arr;
int* brr = (int*)malloc(sizeof(int));
};
int main()
{
Date a(2002, 4, 3);
a.print();
return 0;
}
通过以上两个代码可以总结出:
在类中声明成员变量的时候,可以写成类似定义并初识化的形式(但其实不是,只是声明加给缺省值),后边跟着的值是给的缺省值,如果我们没有写自己的构造函数,编译器自动生成的构造函数会用我们声明时给的缺省值初始化成员变量,这样成员变量就不再是随机值了,这就是c++11解决自动生成的构造函数对内置类型的成员变量不做任何赋值处理的问题。
友元,顾名思义就是“好朋友元”,既然是好朋友那么就是我的东西你可以拿来随便用,但是要注意的是这里的友元不是双向的,而是单向的。具体往下看->
友元又分为友元类和友元函数。
先介绍友元类:
友元类
class Time
{
friend class Date;//声明Date是Time的友元类,那么Date就可以访问Time中的私有成员变量了
Time(int hour)
:_hour(hour)
{
}
private:
int _hour;
};
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
, _t(2)
{
}
void print()
{
cout << _t._hour << endl;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
无声明Date是Time的友元类的代码:
没有声明Date是Time的友元类的时候,Date类中是无法访问Time类中的私有成员变量的
有声明Date是Time的友元类的代码: 可以访问Time中的私有成员变量
注意:例如A是B的友元类,那么A可以访问B中的私有成员变量或者函数。但是B不能访问A中的私有成员变量或函数!!!
1.友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
2.友元关系是单向的,不具有交换性。
3.友元关系不能传递 如果B是A的友元,C是B的友元,则不能说明C时A的友元。
友元函数
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声 明,声明时需要加friend关键字。
注意点:
1.友元函数可访问类的私有和保护成员,但不是类的成员函数
2.友元函数不能用const修饰
3.友元函数可以在类定义的任何地方声明,不受类访问限定符限制
4.一个函数可以是多个类的友元函数
5.友元函数的调用与普通函数的调用和原理相同
例如:getval 是一个类外的普通函数,但是在Date类中声明了其是Date类的友元函数,友元函数可以访问类的私有成员变量,那么就可以通过调用getval函数来打印出类中的_year了
->
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的 成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化、
静态成员变量:
例如:Date类中的静态成员变量x要在类外进行初识化
静态成员函数:
注意点:静态成员函数没有隐藏的this指针,不能访问任何非静态成员
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的 类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。 注意:内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中 的所有成员。但是外部类不是内部类的友元。
特性:
1. 内部类可以定义在外部类的public、protected、private都是可以的。
2. 注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
3. sizeof(外部类)=外部类,和内部类没有任何关系。
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{
}
class Time//Time 类是Date的内部类 内部类可以访问外部类的成员,但是外部类不能访问内部类的任何成员,内部类天生就是外部类的友元类
{
private:
int _time;
};
private:
int _year;
int _month;
int _day;
};
int main()
{
Date a(2000, 2, 2);
return 0;
}
C++通过类,将一个对象的属性与行为结合在一起,使其更符合人们对于一件事物的认知,将属于该对象的 所有东西打包在一起;通过访问限定符选择性的将其部分功能开放出来与其他对象进行交互,而对于对象内 部的一些实现细节,外部用户不需要知道,知道了有些情况下也没用,反而增加了使用或者维护的难度,让 整个事情复杂化。
创作不易,支持一下吧,谢谢啦~~
dzqc xzjz!!!