库尔勒网站建设电话,遵义网站制作的网站,秦皇岛建筑,incapsula wordpress类和对象 类的定义this指针类的6个默认成员函数构造函数析构函数拷贝构造函数赋值运算符重载赋值运算符重载运算符重载const成员 取地址操作符重载const取地址操作符重载 初始化列表explicit关键字static成员匿名对象友元内部类拷贝对象时编译器的优化 类的定义
c类的定义形式… 类和对象 类的定义this指针类的6个默认成员函数构造函数析构函数拷贝构造函数赋值运算符重载赋值运算符重载运算符重载const成员 取地址操作符重载const取地址操作符重载 初始化列表explicit关键字static成员匿名对象友元内部类拷贝对象时编译器的优化 类的定义
c类的定义形式为
class className
{
pubilic://...
private://...
};在类的内部可以定义变量和函数c可以通过3个访问限定符来限制类的成员的访问权限 pubilc:在该作用域的成员在类外可以直接访问 private和protected:在该作用域内的成员在类外不能直接访问 c为了兼容c就将c的结构体提升成了类使用struct和class定义类的唯一区别是class的成员的默认访问权限为private,struct的成员的默认访问权限是public其他的并无区别。 在类里面定义的成员函数编译器会将其当成内联函数来处理但是否展开最终还是取决于编译器。我们建议将短小的函数直接在类里面定义其他的函数则声明和定义分离。 对象是类类型的实例化类类型是对实例化对象的描述对于一个类实例化出的多个对象来说除了成员变量用来存储不一样的数据成员函数都是一样的因此为了节省空间对象只保存成员变量而成员函数则放在公共的代码段。由此一个类的大小只需计算成员变量的大小即可其计算方法与计算结构体的大小的方法一致结构体大小的计算。对于空类编译器为了标识该类的存在给了其一个字节的大小。
this指针
c编译器给每一个非静态成员函数增加了一个隐藏的指针参数该指针指向当前对象当对象调用成员函数时就可以通过该指针访问该对象的成员变量该指针由编译器自行传递不需要用户来完成用户可以在类里面显示使用this指针。
//定义了一个类
class Student
{
pubilc:void Print()//这里有一个默认this指针相当于void Print(Student *this){cout_name_age_sex;//coutthis-_age,显示使用this指针}
private:_name[20]zhangsan;_age20;_sex[7]male
};int main()
{Student s;//调用打印函数打印学生信息s.Print();//相当于s.Print(s)return 0;
}this指针特性 1.this指针的类型为类类型*const(如Student *const this)因此不能给this指针赋值。 2.this指针只能在成员函数内部使用。 3.this指针本质上是成员函数的形参所以对象中不存储this指针同普通函数参数一样存放在栈区VS存放在寄存器。 4.this指针是成员函数参数列表隐藏着的第一个参数。 5.this指针可以为空。
class Student
{
pubilc:void Print(){cout“c”;}
private:_name[20]zhangsan;_age20;_sex[7]male
};int main()
{Student* snullptr;s-Print();//由于成员函数不在对象中此处不需要解引用故代码可以正常执行//此时this指针为空return 0;
}class Student
{
pubilc:void Print(){cout_age;}
private:_name[20]zhangsan;_age20;_sex[7]male
};int main()
{Student*snullptr;s-Print();//该代码编译通过但运行崩溃return 0;
}类的6个默认成员函数
默认成员函数是当用户没有显式定义时编译器自动生成的函数。
构造函数
构造函数是名字与类名相同的函数创建对象时由编译器自动调用用于给数据成员一个初始值即初始化对象该函数在对象整个生命周期内只调用一次。同时构造函数无返回值可以重载在调用无参的构造函数时后面不需要跟括号。
class Student
{
pubilc://函数1Student(int height,int weight)//用户显式定义构造函数{_heightheight;_weightweight;}//函数2无参Student()//构成重载{;}
private:int _height170;int _weight60;
};int main()
{Student s1(64,177);//创建时自动调用构造函数1Student s2;//创建时自动调用构造函数2//s2后面不需要跟括号即不能写成 Student s2(); 否则就成了函数声明
}当用户没有显式定义构造函数时编译器会自动生成一个无参的默认构造函数该默认构造函数对成员变量的处理方式为对内置类型成员不做处理对自定义成员调用其默认构造函数 (默认构造函数只有无参的构造函数、全缺省的构造函数、编译器自动生成的构造函数3种)。
class Date
{
public:Date(int year)//用户显式定义了一个参数不是缺省值的构造函数{_yearyear;}
private:_year;
};class Student
{
pubilc://用户没有显示定义构造函数由编译器生成默认构造函数
private:int* p;//不处理int _height170;//不处理int _weight60;//不处理Date d1;//调用Date类的默认构造函数//由于Date类没有默认构造函数故出错
};默认构造函数只能有一个
class Date
{
public:Date(int year1){cout 有参数 endl;}Date (){cout 无参数 endl;}
private:int _year 60;int _month 0;int _day 0;
};class Student
{
public:private:int height;int weigth;Date d;
};int main()
{Date d1(1);//调用有参的构造函数Date d2;//对重载的构造函数调用不明确出错Student s;//自定义类型调用其默认构造时不明确出错
}由上我们可以得知当成员变量都是内置类型时构造函数可以不写但大多数情况下都要写构造函数。
析构函数
析构函数的功能与构造函数的功能相反用于对成员变量的资源的清理在对象销毁时会自动调用析构函数析构函数不能重载参数列表为空。
class Date
{
public:Date(){_snew int;}~Date()//析构函数{delete s;//进行资源清理}
private:int*_s;
}当用户没有显式定义析构函数时同构造函数一样编译器会自动生成一个默认析构函数该默认析构函数对成员变量的处理方式为对内置类型成员不做处理对自定义成员调用其析构函数。
当类里面没有资源申请时析构函数可以不写使用编译器生成的默认析构函数就可以了。
拷贝构造函数
在用已经存在的类对象创建新对象时会调用拷贝构造函数如当参数传值为一个类对象、返回一个类对象时等。拷贝构造函数是构造函数的一个重载形式参数只有一个且必须是类类型对象的引用。
class Date
{
public://参数一定要是引用如果不是引用使用拷贝构造函数要进行值拷贝//就会又去调用拷贝构造函数从而引发无穷递归Date(const Date d)//拷贝构造函数使用const使代码更健壮{_yeard._year;_monthd._month;_dayd._day;}private:int _year 60;int _month 0;int _day 0;
};int main()
{Date d1;Date d2d1;//调用拷贝构造函数
}如果用户没有显式定义拷贝构造函数默认的拷贝构造函数进行的是浅拷贝值拷贝对成员变量的处理方式为对内置类型成员进行浅拷贝对自定义成员调用其拷贝构造函数。
如果类里面没有涉及到资源的申请时拷贝构造函数可以不写但当涉及到资源申请时拷贝构造函数一定要写否则是浅拷贝容易出错。
class Date
{
public:Date()//构造函数{_snew int;}~Date()//析构函数{delete s;//进行资源清理}
private:int*_s;
}int main()
{Date d1;Date d2d1;//不会再调用构造函数//以上代码运行时出错//Date类里面进行的是浅拷贝d1和d2里面的_s指向同一块空间//对象d1、d2销毁时都要调用其析构函数对同一块空间释放了2次出错return 0;
}赋值运算符重载
赋值运算符重载
当要对一个已经创建好的对象进行赋值操作时需要调用赋值运算符重载函数
class Date
{
public:Date operator(const Date d)//赋值运算符重载{if(this!d){_yeard._year;_monthd._month;_dayd._day;}return *this;}
private:_year;_month;_day;
}int main()
{Date d1;Date d2;d2d1;//调用赋值运算符重载
}该函数要注意以下4点 1.为了符合连续赋值函数需要返回*this,同时为了提高返回的效率需要用到引用返回。 2.为了提升传参效率和增强代码健壮性参数应为引用且使用const修饰。 3.要检测是否是自己给自己赋值。 原因可以参考这里l1dian11的博客 4.赋值运算符只能重载成类的成员函数不能重载成全局函数。
当用户没有显式定义时编译器会自动生成一个默认的运算符重载以值的方式逐字节拷贝对成员变量的处理方式为对内置类型成员直接赋值对自定义成员调用其对应的赋值运算符重载。
运算符重载
c除了支持赋值运算符重载外还支持其他的运算符重载只不过编译器不会自动生成这些运算符重载需要用户显式定义。 其有以下几点需要注意 1.只能重载已有的运算符不能通过其他符号重载新的运算符如不能重载 2.重载类型必须有一个类类型参数防止用户改变该操作符原来的对内置类型的运算 3.以下5个运算符不能重载:
.* :: sizeof ?: .这里说一下比较特殊的运算符重载:
1.前置和后置重载 由于这两个运算符的重载无法直接区分c进行了特殊处理在参数列表增加一个int型参数表示后置
class Date
{
public:Date operator()//表示前置运算符重载{//...}Date operator(int)//表示后置运算符重载{//...}
private:_year;_month;_day;
};2.流插入和流提取的运算符重载 cout是ostream类的对象,cin是istream类的对象
class Date
{
public:ostream operator(ostream out){//...}private:_year;_month;_day;
};//用法如下
Date d;
dcout;虽然重载成功了因为this指针默认占了第一个参数所以其使用方式很奇怪不符合我们使用的习惯因此我们只能将其重载成全局函数。
class Date
{
public://使用友元使类外的函数可以访问类里面的私有成员friend ostream operator(ostream out,const Date d);
private:_year;_month;_day;
};friend ostream operator(ostream out,const Date d)
{//...
}const成员
大多数情况下我们并不希望成员函数拥有对类里面的成员进行修改的权限因此我们希望对this指针使用const进行修饰。
class Date
{
public:void fun() const{//...}以上函数相当于void fun(const Date* const this)//第2个const是this指针自带的
private:_year;_month;_day;
};我们建议只要成员函数不涉及到对成员变量的修改后面都要加上const进行修饰。
取地址操作符重载
用于对一个普通对象取地址
class Date
{
public:Date* operator(){return this;//一般是直接返回this即可//如果写成return 0x11223344;//那么用取地址符获取对象地址时将全都是0x11223344这个地址}
private:_year;_month;_day;
};这个一般不需要重载使用编译器默认生成的即可。
const取地址操作符重载
用于对const修饰的对象取地址
class Date
{
public:const Date* operator() const{return this;//一般是直接返回this即可//如果写成return 0x11223344;//那么用取地址符获取对象地址时将全都是0x11223344这个地址}
private:_year;_month;_day;
};这个一般也不需要重载使用编译器默认生成的即可。
初始化列表
类在实例化成对象时所有的成员变量都会在初始化列表中进行定义并给予变量相对应的值内置类型如果没有显式地写在初始化列表则会在初始化列表中给予其一个随机值对自定义类型会去调用其默认构造函数。初始化列表和构造函数可以混合使用。 c11打了补丁允许其在声明时赋值这些值其实都是缺省值用于给初始化列表。
class Date
{
public:Date(int year,int month,int day):_year(2),_month(2){_day2;}
private:_year1;_month1;_day1;
};int main()
{Date d(3,3,3);//d._year2,d._month2,d._day2;
}类里面成员变量在类中的声明次序就是初始化列表的初始化顺序与其在初始化列表中的顺序无关。
explicit关键字
如果类的构造函数只有一个参数或者除第一个参数无默认值其余均有默认值则该类可以支持隐式转换。
class Date
{
public:Date(int year,int month1,int day1){_yearyear;_monthmonth;_dayday;}
private:_year;_month;_day;
};int main()
{Date d2019;//_year2019,_month1,_day1//将2019转换成Date(2019,1,1),再赋给d
}c11还支持多参数的隐式类型转换
class Date
{
public:Date(int year,int month,int day1){_yearyear;_monthmonth;_dayday;}
private:_year;_month;_day;
};int main()
{Date d{2019,10,11};//_year2019,_month10,_day11
}有时候我们并不希望这种隐式类型转换的发生只需在构造函数前面加上explicit关键字即可但这个关键字不能阻止强制类型转换的发生。
static成员
在类里面以static关键字修饰的成员称为类的静态成员对于静态成员变量其只能在类里面进行声明不能给缺省值。 静态成员有以下特性 1.静态成员为所有类对象所共享不属于某个对象存放在静态区。 2.静态成员变量在类内只是声明必须要在类外定义定义时不需加static关键字。 3.静态成员可以直接通过类名::静态成员或者对象.静态成员来访问 4.静态成员没有this指针不能访问任何非静态成员。 6.静态成员也是类的成员受public、private、protect访问限定符的限制。
class Date
{
public:explicit Date(int year1,int month1,int day1){i;_year year;_month month;_day day;}static int i;
private:int _year;int _month;int _day;};int Date::i 0;int main()
{Date d1;Date d2;Date d3;Date d4;Date d5;cout Date::i endl;//i5return 0;
}匿名对象
c允许匿名对象可以拥有充当临时变量的作用其生命周期只在这一行该行执行完就销毁。
class Date
{
public:Date(int year,int month,int day){_year year;_month month;_day day;}
private:int _year;int _month;int _day;};int main()
{Date d1 Date(1, 2, 3);//匿名对象Date d2 {1,2,3};//隐式转换return 0;
}友元
友元分为友元函数和友元类 友元函数是定义在类外部的普通函数不属于任何类在类里面声明可以直接访问类的私有成员其有以下特性 1.友元函数不能用const修饰 2.友元函数可以定义在类定义的任何地方声明不受访问限定符的限制 3.一个函数可以是多个类的友元函数 4.友元函数与普通函数的调用原理相同
友元类的所有成员函数都是另一个类的友元函数都可以访问另一个类里面的私有成员。 友元关系是单向的没有传递性不能继承。 虽然友元为访问私有成员提供了便利但破坏了封装增加了耦合度不建议过多使用。
内部类
内部类是指定义在另一个类内部的类他是一个独立的类不属于外部类也不能通过外部类的对象访问内部类的成员可以认为外部类对内部类没有任何优越的访问权限。但内部类却是外部类的友元类即内部类可以通过类外部的对象参数访问外部类的所有成员。 需要注意外部类的大小和内部类没有任何关系。
拷贝对象时编译器的优化
大部分编译器会对连续的构造或拷贝构造进行优化如
连续的构造构造优化为一个构造 连续的构造拷贝构造优化为一个构造 连续的拷贝构造拷贝构造优化为一个拷贝构造 不同的编译器的优化方式和程度不同。