网站开发设计课程教案,公众号模板网站,微站开发,济南营销型网站建设哪家好【C初阶】之类和对象#xff08;下#xff09; ✍ 再谈构造函数#x1f3c4; 初始化列表的引入#x1f498; 初始化列表的语法#x1f498; 初始化列表初始化元素的顺序 #x1f3c4; explicit关键字 ✍ Static成员#x1f3c4; C语言中的静态变量#x1f3c4; C中的静… 【C初阶】之类和对象下 ✍ 再谈构造函数 初始化列表的引入 初始化列表的语法 初始化列表初始化元素的顺序 explicit关键字 ✍ Static成员 C语言中的静态变量 C中的静态成员 特性 ✍ 友元 友元函数 友元类 友元类的特性 ✍ 内部类 内部类的特性 ✍ 匿名对象✍ 拷贝对象时的一些编译器优化 隐式类型连续的构造拷贝构造-优化为直接构造 隐式类型生成的对象具有常性 一个表达式中连续的构造拷贝构造-优化为一个构造 一个表达式中连续的拷贝构造拷贝构造-优化一个拷贝构造 一个表达式中连续的拷贝构造赋值重载-无法优化 Release下不是一个表达式的连续的构造拷贝构造--构造函数部分会优化 ✍ 再次理解类和对象 类和对象的理解 类的组成 博客主页 小镇敲码人 热门专栏C初阶 欢迎关注点赞 留言 收藏 任尔江湖满血骨我自踏雪寻梅香。 万千浮云遮碧月独傲天下百坚强。 男儿应有龙腾志盖世一意转洪荒。 莫使此生无痕度终归人间一捧黄。 ❤️ 什么你问我答案少年你看下一个十年又来了 前言本篇博客接上篇类和对象中。接着来跟着博主学习C类和对象下的一些特性吧
✍ 再谈构造函数 构造函数体类的操作我们只能叫做赋值不能叫做初始化因为初始化只有一次而赋值可以在构造函数体类无数次。 假设现在我们的类里面有const成员和引用成员会发生什么情况呢 奇怪这里我们明明在构造函数内给year和month赋值了呀为什么还是会报错呢报错说我们没有初始化说明初始化并不发生在构造函数内。
我们来回顾一下const对象和引用必须在定义的时候就初始化 但是在类里面它们只是声明了一下而已并没有定义真正定义是在那个类实例化一个对象之后。而且const对象和引用在初始化之后就不能修改了这样你应该就可以理解为什么不允许在构造函数体类初始化这些对象了因为我无法判断你是否第二次又对它进行了初始化如果真那样做就逻辑不自洽了。 初始化列表的引入 那不能在构造函数函数体里面初始化在哪里初始化呢答案是在初始化列表里面初始化。 初始化列表的语法
class Date
{
public:Date(int month_):year(3),month(month_),day(2){}
private:const int year;int month;int day ;
};在原先的普通构造函数的函数名后面加上冒号然后对应的成员变量后面加括号括号里面是其要初始化的值不同变量间使用逗号分隔。
注意如果是const对象或者引用类型的初始化必须走初始化列表拷贝构造函数也可以用来创建一个对象这些类型也必须走初始化列表否则就会报错。 这里就算我们使用了初始化列表编译器隐式生成的拷贝赋值函数也会被删除。 除了const和引用类型的变量必须走初始化列表没有无参的构造函数的自定义类型也必须走初始化列表因为要给它传参数也不能放在构造函数中因为自定义类型的构造函数只能在初始化的时候调用。
有无参的构造函数可以访问的自定义类型会在定义的时候自动调用 没有无参的构造函数时如果你不再初始化列表里调用就会报错 这个时候我们应该在定义的时候给这个自定义类型传一个参数 初始化列表初始化元素的顺序 这个顺序与我们实际在初始化列表中的元素初始化顺序无关只与元素的声明顺序有关。 explicit关键字 explicit关键字可以防止只需要传一个参数只有一个或者后面都是缺省参数的普通的构造函数发生隐式类型的转换。 请看下面代码
class Date
{
public:Date(int year_){year year_;month 3;day 2;}~Date(){}
private:int year;int month;int day ;
};int main()
{Date y(2023);y 2022;return 0;
}你是否会认为其会导致编译错误呢其实不然这里编译器会将2022隐式类型转化为Date类型我们可以通过调试发现其确实调用了构造函数 并且y的year变成了2022这里是先调用了一次构造函数再去调用了编译器默认生成的赋值构造函数。 如果我们在普通构造函数前加上explicit就不支持隐式类型转换了。 ✍ Static成员 C语言中的静态变量 我们在C语言里面也有Static类型的变量我们把它称作静态成员变量这个变量有一个特征就算出了函数作用域它不会销毁生命周期只有程序结束它才会结束。 void f()
{static int a 3;int b 3;a;b;
}
int main()
{f();f();return 0;
}连续调用两次f()函数a为多少呢 可以看到a变成了5说明a执行了两次在第一次出函数后它没有销毁并且静态变量只会定义一次。 C中的静态成员 在c的类里面也会有静态的成员它和C语言中的函数中的静态变量有什么区别和相同之处呢呢? C类里面有两类静态成员它们是静态成员函数和静态成员变量。
class Date
{Date(){count;}Date(const Date x){count;}static int Getcount(){return count;}~Date(){count--;}
private:static int count;
};int Date::count 0;int main()
{return 0;
}上述程序可以用来计算类实例化了多少个对象。下面外面来具体阐述一下它们的特性。 特性
静态成员函数和静态成员变量也受访问限定符的限制。静态成员变量必须要在类外面初始化初始化不要求访问限定符为public但是下次调用的时候需要它在类外面可以访问且不能多次初始化。 静态成员变量不能在声明的时候给缺省值因为它的初始化不走初始化列表而是在类外面通过类名::进行访问并初始化。 4、静态成员函数和静态成员变量在类外面访问假设是公有的可以通过实例化对象访问或者是类名::访问。 5. 静态成员变量和函数为所有类对象所共享不属于某个具体的对象存放在静态区。 6. 静态成员函数类没有this指针不能访问任何非静态成员但是可以访问静态成员。 7. 但是非静态的成员函数可以调用静态成员。 想一想为什么需要静态函数呢就拿刚刚计算实例化了多少对象的程序来说想知道没有实例化对象时count为多少如何知道呢此时不能实例化对象且count为private就只能借助于类的静态成员函数来完成。
✍ 友元 友元提供了一种突破访问限定符和类域限定符的作用但是破坏了程序的耦合度不易过多使用友元分为友元函数和友元类。 友元函数 如果一个函数是一个类的友元函数那么它就可以访问这个类的私有成员。 友元函数是为了解决重载函数但是this指针默认在第一个参数的情况我们应该让cout在第一个参数的位置因为运算符重载函数的操作数和运算符重载函数的参数是一一对应的。 这里我们用代码来分析
class Date
{
public:Date(){year 2022;month 2;day 1;}void operator(std::ostream cout){}
private:int year;int month;int day;
};int main()
{Date x;std::cout x;
}由于左操作数是cout但是类的成员函数的默认第一个参数是this指针所以这里会报错。cout的类型是ostream。
我们可以使用友元函数解决这个问题
class Date
{
public:friend std::ostream operator(std::ostream cout_, const Date x);Date(){year 2022;month 2;day 1;}private:int year;int month;int day;
};std::ostream operator(std::ostream cout_, const Date x)
{cout_ x.year x.month x.day std::endl;return cout_;
}int main()
{Date x;std::cout x std::endl;return 0;
}运行结果 这里我们流提取函数之所以要返回cout_是为了支持连续的调用这个函数。 std::cout x std::endl;这段代码理解就是先去调用operator(std::ostream cout_, const Date x)返回一个cout_就变成了std::cout std::endl;继续调用流提取重载函数。流插入函数是同样的道理。
友元函数只需要在相应的类里面声明一个函数不受访问限定符的限定并在函数前面加上friend关键词就可以了。注意你不能把友元函数的定义写在类里面因为它不是类的成员。 友元函数不能用const修饰因为它并不是类的成员函数。一个函数可以是多个类的友元函数。 友元类 友元类是指的是假设现在有两个类A、B,如果你声明了A是B的友元类在A中如果创建了B的实例化对象就可以调用B的私有成员私有变量和函数。 下面我们用代码来验证一下
// 定义一个名为B的类
class B
{ // 声明类A为B的友元类这样A可以访问B的私有和保护成员 friend class A; public: // 公共成员函数ff可以在类的外部调用 void ff() { // 函数体为空没有实际功能 } private: // 在类的私有部分不能声明友元类友元声明应该放在类的公有或保护部分 // 私有成员函数f只能在B类内部或B的友元中调用 void f() { // 函数体为空没有实际功能 } // 私有整型成员变量a初始化为3 int a 3; // 私有整型成员变量b初始化为4 int b 4;
}; // 定义一个名为A的类
class A
{
public: // 公共成员函数fff可以在类的外部调用 void fff() { // 调用B类对象x的公共成员函数ff x.ff(); // 调用B类对象x的私有成员函数f // 由于A是B的友元类所以可以访问B的私有成员 x.f(); } private: // A类中含有一个B类的对象x作为私有成员 B x;
}; int main()
{ // 创建A类的对象y A y; // 程序正常结束返回0 return 0;
}这段代码是没有问题的从中我们可以发现友元类的一些特性。 友元类的特性
A是B的友元类但是B不是A的友元类友元关系是单向的。A是B的友元类B是C的友元类但是A不是C的友元类友元关系是不能传递的。声明友元类和声明友元函数的区别是类名前面多了class。friend class A;友元类的声明可以在这个类的任何位置。 ✍ 内部类 内部类就是在一个类的里面创建了一个类但是这个类是独立的和外部类没有任何的关系但是受它的访问限定符的限制如果是private在外面无法实例化对象它也不属于外部类不能通过外部类的this指针访问内部类的任何成员。外部类对内部类没有任何优越的访问权限。 但是内部类是外部类的友元类访问外部类有优越的访问权限。 下面一段代码帮助你理解内部类
#include iostream // 引入标准输入输出库用于使用std::cout class A
{
public: // A类的构造函数 A() { // 构造函数体为空 } // A类的公有成员函数f void f() { // 函数体为空 } // A类的内部类B class B { public: // B类的公有成员函数f接收一个A类的引用作为参数 void f(A x) { // 输出A类对象的c成员变量和B类的静态成员变量count std::cout x.c count; // 调用A类对象的公有成员函数f x.f(); // 尝试调用A类对象的成员函数ff但ff是A类的私有成员函数这里会导致编译错误 x.ff(); } private: // B类的私有整型成员变量b初始化为3 int b 3; }; private: // A类的私有成员函数ff void ff() { // 函数体为空 } // A类的静态整型成员变量count用于在多个A类对象间共享数据 static int count; // A类的私有整型成员变量c初始化为3 int c 3;
}; // 初始化A类的静态成员变量count赋值为3
int A::count 3; int main()
{ // 创建A类的内部类B的对象b A::B b; // 创建A类的对象a A a; // 调用B类对象b的公有成员函数f并传入A类对象a的引用 b.f(a); // 程序正常结束返回0 return 0;
}内部类的特性
创建内部类的实例化对象必须要使用外部类的类名::.内部类不能直接在类里面创建外部类的对象要从外面传一个过来外部类的成员通过传过来的对象访问静态成员可以直接访问。 sizeof()计算外部类的大小和内部类没有任何关系。 这里也进一步验证了静态变量是放在静态区的没有和类的普通成员变量存放在一起也不算在类的大小里面。
局部的const变量是存在栈上的 运行结果 全局的const变量放在常量区。
内部类可以定义在外部类的public、protected、private都是可以的。但是它受外部类类域的限制。如果使用private在外面就无法实例化内部类的对象了。 ✍ 匿名对象 匿名对象就是没有名字的对象是这个对象没有名字不是这个类没有名字。 我们写一段代码来举个例子 class Date
{
public:Date(){std::cout Date() std::endl;}~Date(){std::cout ~Date() std::endl;}
private:int year;int month;int day;
};int main()
{Date x;Date ();return 0;
}Date ()就是一个匿名对象但是它的作用域只有一行出了这一行它就调用析构函数销毁了。 匿名对象的一些优势
class Date
{
public:Date(){std::cout Date() std::endl;}Date(const Date x){}~Date(){std::cout ~Date() std::endl;}
private:int year;int month;int day;
};int main()
{Date y;Date x(y);Date z(Date ());return 0;
}同样是使用一个类初始化另外一个类调用拷贝构造函数匿名对象可以不用给类取名字。
✍ 拷贝对象时的一些编译器优化 我们都知道C里面有很多构造函数但是有时候这些构造函数在一起的时候编译器会省略一些没必要产生的构造以达到优化的效果。主要优化是发生在传参和传返回值中。 隐式类型连续的构造拷贝构造-优化为直接构造 隐式类型生成的对象具有常性
using namespace std;
class Date
{
public:Date(int year, int month 2, int day 1) ://普通的构造函数year_(year),month_(month),day_(day){cout Date(int year, int month 2, int day 1) endl;}Date(const Date x) ://拷贝构造函数year_(x.year_),month_(x.month_),day_(x.day_){cout Date(const Date x) endl;}Date operator(const Date x)//拷贝赋值函数{year_ x.year_;month_ x.month_;day_ x.day_;cout operator(const Date x) endl;return *this;}~Date()//析构函数{cout ~Date endl;}
private:int year_;int month_;int day_;
};void f(const Date x)
{}
int main()
{f(3);return 0;
}3–Date会去调用普通构造函数隐式类型转化为了Date类型但是具有常性如果不加const就会报错。 回归正题。请看下面一段代码
using namespace std;
class Date
{
public:Date(int year, int month 2, int day 1) ://普通的构造函数year_(year),month_(month),day_(day){cout Date(int year, int month 2, int day 1) endl;}Date(const Date x) ://拷贝构造函数year_(x.year_),month_(x.month_),day_(x.day_){cout Date(const Date x) endl;}Date operator(const Date x)//拷贝赋值函数{year_ x.year_;month_ x.month_;day_ x.day_;cout operator(const Date x) endl;return *this;}~Date()//析构函数{cout ~Date endl;}
private:int year_;int month_;int day_;
};void f(Date x)
{}
int main()
{f(3);return 0;
}这里调用f(3)正常应该是先构造函数将3隐式类型转化为Date类型然后再调用拷贝构造函数去初始化x这里编译器做了优化连续的构造拷贝构造-优化为直接构造。 一个表达式中连续的构造拷贝构造-优化为一个构造
using namespace std;
class Date
{
public:Date(int year, int month 2, int day 1) ://普通的构造函数year_(year),month_(month),day_(day){cout Date(int year, int month 2, int day 1) endl;}Date(const Date x) ://拷贝构造函数year_(x.year_),month_(x.month_),day_(x.day_){cout Date(const Date x) endl;}Date operator(const Date x)//拷贝赋值函数{year_ x.year_;month_ x.month_;day_ x.day_;cout operator(const Date x) endl;return *this;}~Date()//析构函数{cout ~Date endl;}
private:int year_;int month_;int day_;
};void f(Date x)
{}
int main()
{f(Date(2022));return 0;
}实际调用的构造函数 如果不是一个表达式会优化吗 可以看到并没有优化。 一个表达式中连续的拷贝构造拷贝构造-优化一个拷贝构造
using namespace std;
class Date
{
public:Date(int year, int month 2, int day 1) ://普通的构造函数year_(year),month_(month),day_(day){cout Date(int year, int month 2, int day 1) endl;}Date(const Date x) ://拷贝构造函数year_(x.year_),month_(x.month_),day_(x.day_){cout Date(const Date x) endl;}Date operator(const Date x)//拷贝赋值函数{year_ x.year_;month_ x.month_;day_ x.day_;cout operator(const Date x) endl;return *this;}~Date()//析构函数{cout ~Date endl;}
private:int year_;int month_;int day_;
};void f(Date x)
{}Date f1()
{Date x(2022);return x;
}
int main()
{Date y f1();return 0;
}这里正常应该会调用一个普通的构造函数两个拷贝构造函数我们看实际的情况 实际只调用一次拷贝构造函数编译器优化了。 一个表达式中连续的拷贝构造赋值重载-无法优化
using namespace std;
class Date
{
public:Date(int year, int month 2, int day 1) ://普通的构造函数year_(year),month_(month),day_(day){cout Date(int year, int month 2, int day 1) endl;}Date(const Date x) ://拷贝构造函数year_(x.year_),month_(x.month_),day_(x.day_){cout Date(const Date x) endl;}Date operator(const Date x)//拷贝赋值函数{year_ x.year_;month_ x.month_;day_ x.day_;cout operator(const Date x) endl;return *this;}~Date()//析构函数{cout ~Date endl;}
private:int year_;int month_;int day_;
};void f(Date x)
{}Date f1()
{Date x(2022);return x;
}
int main()
{Date y(2023);y f1();return 0;
}这段代码正常应该是2个构造函数和1个赋值构造、1个拷贝构造函数并且连续的拷贝构造和赋值构造在一个表达式中能优化吗我们拭目以待 可以看到并没有优化。 Release下不是一个表达式的连续的构造拷贝构造–构造函数部分会优化
请看下面代码
using namespace std;
class Date
{
public:Date(int year, int month 2, int day 1) ://普通的构造函数year_(year),month_(month),day_(day){cout Date(int year, int month 2, int day 1) endl;}Date(const Date x) ://拷贝构造函数year_(x.year_),month_(x.month_),day_(x.day_){cout Date(const Date x) endl;}Date operator(const Date x)//拷贝赋值函数{year_ x.year_;month_ x.month_;day_ x.day_;cout operator(const Date x) endl;return *this;}~Date()//析构函数{cout ~Date endl;}
private:int year_;int month_;int day_;
};void f(Date x)
{}Date f1()
{Date x(2022);return x;
}
int main()
{f1();return 0;
Debug下的运行结果 Release下的 可以看到Release下的优化更猛。
✍ 再次理解类和对象 计算机是不认识我们现实世界中的实物的计算机只认识二进制格式的数据。如果想要让计算机认识现实生活中的实体用户必须通过某种面向对象的语言对实体进行描述然后通过编写程序创建对象后计算机才可以认识。 类和对象的理解
1、类和对象的理解 类指的是对客观事物的一种描述是对现实中一类具有共同属性和行为的事物的抽象。
对象指的是具体存在的事物是能够看得到摸的着的真实存在的实体。 类的组成
而C作为典型的面向对象的语言就使用类来描述现实世界中的事物类分为属性和行为两部分。属性是指的是类中的成员变量–事物的特征行为是指的是类中的一些方法函数–事物能执行的操作。