网站建设公司的案例,网站空间去哪买,asp.net网站开发pdf,东莞seo排名收费文章目录1 再谈构造函数1.1 构造函数体赋值1.2 初始化列表1.3 explicit关键字2 static成员2.1 概念2.2 特性3 友元3.1 友元函数#xff08;流插入#xff08;#xff09;及流提取#xff08;#xff09;运算符重载#xff09;3.2 友元类4 内部类5 匿名对…
文章目录1 再谈构造函数1.1 构造函数体赋值1.2 初始化列表1.3 explicit关键字2 static成员2.1 概念2.2 特性3 友元3.1 友元函数流插入及流提取运算符重载3.2 友元类4 内部类5 匿名对象6 拷贝对象时的一些编译器优化7 再次理解类和对象1 再谈构造函数
1.1 构造函数体赋值
在创建对象时编译器通过调用构造函数给对象中的各个成员变量一个合适的初始值。如下
class Date{
public:Date(int year, int month, int day){_year year;_month month;_day day;}
private:int _year;int _month;int _day;
};虽然上述构造函数调用后对象中已经有了一个初始值但是不能将其称之为对对象中成员变量的初始化构造函数体中的语句只能将其称为赋初值而不能称作初始化。因为初始化只能初始化一次而构造函数体内可以多次赋值。 1.2 初始化列表
前言
我们知道当我们编写一个类时只是对这个类整体进行了一个声明只有当我们通过类的实例化创建出该类类型对象才算是完成一个对象的定义但实际上这只能算是对对象整体的定义那其中的每个成员变量又是在什么时候定义的呢如下以一个类为例这里类中没有主动编写的构造函数在定义对象时调用编译器默认生成的构造函数。问题来了当我们在类中增加const成员变量后再运行程序会发现编译错误原因是作为内置类型成员变量编译器默认生成的构造函数不会对其进行处理而const修饰的变量必须在定义的时候初始化注意在C98时还不能在成员变量声明时给缺省值此时直接定义对象const成员变量没有实现初始化因此会发生编译报错。
class A {
private:int _a1;int _a2;//const int _x; //const变量必须在定义的时候初始化//const int _x 0; //C98不支持缺省值
};int main() {A aa; //对象整体的定义每个成员变量什么时候定义return 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;
};初始化列表的相关注意事项 拷贝构造函数也有初始化列表。 每个成员变量在初始化列表中只能出现一次换句话说初始化只能初始一次。如果某个成员变量没有在初始化列表中则再使用声明时的缺省值进行初始化如果缺省值也没有则以随机值初始化。 类中如果包含以下成员必须放在初始化列表位置进行初始化 引用成员变量必须在定义时进行初始化const成员变量必须在定义时进行初始化没有默认构造函数的自定义类型成员 示例
class A{
public://带参构造函数A(int a):_a(a){}private:int _a;
};class B{
public:B(int a, int ref):_aobj(a),_ref(ref),_n(10){}private:A _aobj; //没有默认构造函数int _ref; //引用const int _n; // const
};尽量使用初始化列表初始化因为不管你是否使用初始化列表类中每个成员变量都会先使用初始化列表进行初始化。对于内置类型成员变量如果没有显示使用初始化列表则以缺省值进行初始化如果缺省值也没有则以随机值初始化对于自定义类型成员如果显示使用了初始化列表则调用其对应构造函数如果没有显示使用初始化列表则调用其默认构造函数如果默认构造函数也没有则不能完成初始化也就不能通过编译 。 示例
class Time{
public:Time(int hour 0):_hour(hour){cout Time() endl;}private:int _hour;
};class Date{
public:Date(int day){}private:int _day;Time _t;
};int main()
{Date d(1);
}
//输出Time()成员变量在类中的声明次序就是其在初始化列表中的初始化顺序与其在初始化列表中的先后次序无关。
class A{
public:A(int a):_a1(a) //先定义_a1, _a2(_a1){}void Print() {cout _a1 _a2 endl;}private:int _a2; //先声明_a2int _a1;
};int main() {A aa(1);aa.Print();
}//输出1 -858993460
//即按声明顺序进行初始化在_a1在_a2之后初始化而_a2又以_a1的值进行初始化此时_a1还是随机值所以_a2为随机值1.3 explicit关键字 构造函数不仅可以构造与初始化对象对于单个参数或者除第一个参数无默认值而其余参数均有默认值的构造函数还可以进行隐式类型转换。而如果不想使其能进行隐式类型转换可以用关键字 explicit 修饰构造函数。 示例
class A {
public://单参构造函数 - 支持隐式类型转换A(int a):_a1(a){cout A(int a) endl;}不支持隐式类型转换//explicit A(int a)// :_a1(a)//{// cout A(int a) endl;//}//多参构造函数 - C98不支持隐式类型转换C11可以以{参数……}的方式支持A(int a1, int a2):_a1(a1),_a2(a2){}不支持隐式类型转换//explicit A(int a1, int a2)// :_a1(a1)// , _a2(a2)//{}A(const A aa) :_a1(aa._a1){cout A(const A aa) endl;}private:int _a2;int _a1;
};int main() {A aa1(1); //单参构造函数//下面语句发生了隐式类型转换通过创建一个临时变量将1转换成A类类型对象再用临时的对象拷贝创建aa2对象//按理来说运行程序下面语句会调用一次构造函数加一次拷贝构造函数//但实际输出表示只调用了一次构造函数这是因为编译器进行了优化直接用1构造了aa2对象A aa2 1; //下面语句如果不加const则无法通过编译而加了const则编译通过//正是因为隐式类型转换产生了临时对象而临时对象具有常属性//因此需要用const修饰才能进行引用这也侧面说明了隐式类型转换的过程中产生了临时变量const A ref 2;A aa3(1, 2);//多参构造函数A aa4 { 1, 2 };//隐式类型转换 - C11支持C98不支持return 0;
}2 static成员
2.1 概念 声明为 static 的类成员称为类的静态成员用 static 修饰的成员变量称之为静态成员变量用 static 修饰的成员函数称之为静态成员函数。静态成员变量一定要在类外进行初始化。 示例要求实现一个类计算程序中创建出了多少个类对象。
① 方法一使用全局变量计数
错误示例
#include iostreamusing namespace std;int count 0;class A {
public://创建对象要么使用构造函数要么使用拷贝构造函数A() { count; } //只要调用构造函数就让计数值加一A(const A t) { count; } //调用拷贝构造函数也让计数值加一~A() { --count; } //如果对象被析构则计数值减一
};int main() {cout count endl;A a1, a2;A a3(a1);cout count endl;return 0;
}上述代码运行后报错指出count是不明确的符号这是因为在C的 xutility(5263,45) 文件中有个与 count 的同名函数 std::count(const _InIt,const _InIt,const _Ty ) 而我们选择将整个 std 命名空间展开这就造成了命名冲突。基于此可以做出以下修改只将用到 cout 及 endl 展开。
正确写法
#include iostream
using std::cout;
using std::endl;int count 0;class A {
public://创建对象要么使用构造函数要么使用拷贝构造函数A() { count; } //只要调用构造函数就让计数值加一A(const A t) { count; } //调用拷贝构造函数也让计数值加一~A() { --count; } //如果对象被析构则计数值减一
};int main() {cout count endl;A a1, a2;A a3(a1);cout count endl;return 0;
}可以看到使用这种方法统计创建的对象要稍微复杂一些且因为全局变量可以在任意位置被修改所以存在安全隐患。
② 方法二使用静态成员变量计数
#include iostreamusing namespace std;class A{
public://创建对象要么使用构造函数要么使用拷贝构造函数A() { _scount; } //只要调用构造函数就让计数值加一A(const A t) { _scount; } //调用拷贝构造函数也让计数值加一~A() { --_scount; } //如果对象被析构则计数值减一//受访问限定符限制为了保证封装性提供GetACount()函数来获取静态成员变量值//静态成员函数static int GetACount1() { return _scount; }//非静态成员函数也可以调用静态成员函数int GetACount2() { return A::GetACount1(); }//静态成员函数只有在包含类类型对象参数时才能用对象调用非静态成员函数static int GetACount3(A a) { return a.GetACount2(); }private:static int _scount;//静态成员变量
};int A::_scount 0;//受类域限制需以 类名:: 的方式访问int main(){cout A::GetACount1() endl; // 类名::静态成员方式调用A a1, a2;A a3(a1);cout a3.GetACount1() endl; // 对象.静态成员方式调用A a4[10];//对象数组cout a3.GetACount2() endl;//非静态成员函数调用静态成员函数cout A::GetACount3(a3) endl;//静态成员函数调用非静态成员函数return 0;
}//输出0 3 13 132.2 特性
静态成员为所有类对象所共享不属于某个具体的对象存放在静态区。静态成员变量必须在类外定义定义时不添加 static 关键字类中只是声明。类静态成员即可用 类名::静态成员 或者 对象.静态成员 的方式访问。静态成员函数没有隐藏的 this指针不能访问任何非静态成员。静态成员也是类的成员受public、protected、private访问限定符的限制。非静态成员函数可以调用静态成员函数静态成员函数只有在包含类类型对象参数时采用通过对象调用非静态成员函数。 3 友元 友元提供了一种突破封装的方式有时提供了便利。但是友元会增加耦合度破坏了封装所以友元不建议过多使用。 友元分为友元函数和友元类 3.1 友元函数流插入及流提取运算符重载
以下以实现对 流插入运算符 和 流提取运算符 重载为例说明友元函数使用
以日期类为例可以看到当我们想输出日期时通常时会先编写一个成员函数然后通过对象去调用成员函数来输出相应的年、月、日。我们知道在C中通常使用 cin 和 cout 进行内置类型的输入输出那能不能也使用这种方式来对自定义类型对象进行输入输出呢答案是当然可以。
通过 cplusplus 网站查看可以发现实际上 cout 和 cin 分别是 ostream 和 istream 类型的对象而之所以在C中 cin 和 cout 输入输出可以自动识别类型是因为在 istream 和 ostream 类中分别实现了的对 流提取运算符 和 流插入运算符 的重载。也就是说如果我们也能实现对这两个运算符的重载那就可以采用 cin 和 cout 的方式进行自定义类型对象的输入输出了。 但这里需要注意的是该运算符重载是实现在 istream 和 ostream 这两个类中的而这两个类是无法被修改的因此我们只能选择在全局实现这两个运算符的重载或是在需要用到的该运算符的自己编写的类中进行运算符重载。
考虑到为了能访问对象中的私有成员我们通常会将运算符重载实现在对应的类中如下
class Date{
public:Date(int year 2023, int month 1, int day 1){_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, 1);//cout d1; //编译报错d1 cout; //输出2023-2-1d1.operator(cout); //输出2023-2-1return 0;
}但运行程序发现我们采用 cout d1; 的方式输出日期时会发生编译报错而采用 d1 cout; 或 d1.operator(cout); 的方式却可以正常输出。这是因为当我们在日期类中实现运算符重载时默认第一个参数即为隐含参数 *this 而对于双目操作符来说第一个参数即为左操作数。但是这样的输出方式与我们平时的输出写法有所差异那怎么能采用 cout d1; 的方式进行输出呢
于是我们考虑将运算符重载实现在类外这样我们就可以主动使 ostream 类对象作为第一个参数而日期类对象为第二个参数。但这样还面临一个问题我们无法在类外访问私有成员当然我们也可以将成员变为公有但这会失去封装性一般不建议这样处理此外也可以通过设置对应的 Get_year() 等公有成员函数来在类外获取私有成员而还有一种方法则是使用 friend 修饰函数即友元函数可以理解为经过修饰后该函数成为了对应类的朋友因此可以访问类中私有成员。此外还有一点需要注意我们平常使用的流插入运算符是可以连续使用的也就是说还运算符重载应该要有返回值返回 ostream 类型对象的引用。如下所示
class Date{//友元函数声明friend ostream operator(ostream out, const Date date);public:Date(int year 2023, int month 1, int day 1){_year year;_month month;_day day;}//void operator(ostream out) {// out _year - _month - _day endl;//}private:int _year;int _month;int _day;
};ostream operator(ostream out, const Date date) {out date._year - date._month - date._day endl;return out;
}int main() {Date d1(2023, 2, 1);Date d2(2023, 2, 7);cout d1 d2; //输出://2023-2-1//2023-2-7return 0;
}同样的接下来我们也可以实现流提取运算符重载如下
class{friend istream operator(istream in, Date date);
public://其它方法
privateint _year;int _month;int _day;
};istream operator(istream in, Date date) {in date._year date._month date._day;return in;
}int main() {Date d1;cin d1;cout d1;return 0;
}说明
友元函数可以直接访问类的私有和保护成员但它不是类的成员函数它是定义在类外部的普通函数不属于任何类但需要在类的内部声明声明时需要加 friend 关键字。友元函数不能用 const 修饰。因为 const 只能修饰成员函数更直接的说 const 是用来修饰 this指针 的而友元函数没有 this指针。友元函数可以在类定义的任何地方声明不受类访问限定符限制。一个函数可以是多个类的友元函数。友元函数的调用与普通函数的调用原理相同。 3.2 友元类
友元类的所有成员函数都可以是另一个类的友元函数都可以访问另一个类中的非公有成员。友元关系是单向的不具有交换性。 如下在 Time 类中声明 Date 类为其友元类那么可以在 Date 类中直接访问 Time 类中的私有成员变量但想在 Time 类中访问 Date 类中的私有成员变量则不行。
class Time{friend class Date; //声明日期类为时间类的友元类则在日期类中就可直接访问Time类中的私有成员变量
public:Time(int hour 0, int minute 0, int second 0): _hour(hour), _minute(minute), _second(second){}private:int _hour;int _minute;int _second;
};class Date{
public:Date(int year 2023, int month 1, int day 1): _year(year), _month(month), _day(day){}void SetTimeOfDate(int hour, int minute, int second){//直接访问时间类私有的成员变量_t._hour hour;_t._minute minute;_t._second second;}private:int _year;int _month;int _day;Time _t;
};友元关系不能传递。 如果 C 是 B 的友元B 是 A 的友元也不能说明 C 是 A 的友元。友元关系不能继承。 4 内部类 概念如果一个类定义在另一个类的内部这个类就叫做内部类。 内部类是一个独立的类它不属于外部类更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越访问权限。 注意内部类就是外部类的友元类。 内部类可以通过外部类的对象参数来访问外部类中的所有成员。但外部类不是内部类的友元。 特性
内部类可以定义在外部类的public、protected、private任意位置。内部类可以直接访问外部类中的 static 成员不需要外部类的对象/类名。sizeof(外部类) 外部类 和内部类没有任何关系。
示例
class A {
private:static int k;int h;public:class B { // B天生就是A的友元private:int _b 2;public:void foo(const A a) {cout k endl;//OKcout a.h endl;//OK}};
};int A::k 1;int main() {A::B b;b.foo(A());cout sizeof(A) endl; //输出:4其中静态成员变量存储在静态区return 0;
}5 匿名对象
class A{
public:A(int a 0):_a(a){cout A(int a) endl;}~A(){cout ~A() endl;}
private:int _a;
};class Solution {
public:int Sum_Solution(int n) {//...return n;}
};int main(){A aa1;// 不能这么定义对象因为编译器无法识别下面是一个函数声明还是对象定义//A aa1();// // 但是我们可以这么定义匿名对象匿名对象的特点不用取名字// 但是他的生命周期只有这一行我们可以看到下一行他就会自动调用析构函数A();A aa2(2);// 匿名对象在这样场景下就很好用Solution().Sum_Solution(10);return 0;
}输出结果 6 拷贝对象时的一些编译器优化 在传参和传返回值的过程中一般编译器会做一些优化减少对象的拷贝在一些场景下还是非常有用的。 示例
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;}~A(){cout ~A() endl;}private:int _a;
};void f1(A aa)
{}A f2(){A aa;return aa;
}int main(){// 传值传参A aa1;f1(aa1);cout endl;// 传值返回f2();cout endl;// 隐式类型连续构造拷贝构造-优化为直接构造f1(1);// 一个表达式中连续构造拷贝构造-优化为一个构造f1(A(2));cout endl;// 一个表达式中连续拷贝构造拷贝构造-优化为一个拷贝构造A aa2 f2();cout endl;// 一个表达式中连续拷贝构造赋值重载-无法优化aa1 f2();cout endl;return 0;
}结果输出 说明 不同的编译器的优化程度不同从上述结果也可以看出本文中所使用的VS2022的编译器的优化程度比较大。对于函数 f2 来说不优化的情况下应该是调用一个构造函数和一个拷贝构造函数而这里直接优化成了一次构造显然优化方式稍有些激进因为函数 f2 中的对象构造和返回是分开的为了避免中间可能还有使用对象的地方保守些的编译器在此是不做优化的。 总结
关于对象返回的总结 接收返回值对象时尽量使用拷贝构造方式接收不要赋值接收函数中返回对象时尽量返回匿名对象。 关于函数传参的总结 尽量使用 const 类型 的方式传参。 7 再次理解类和对象
现实生活中的实体计算机并不认识计算机只认识二进制格式的数据。如果想要让计算机认识现实生活中的实体用户必须通过某种面向对象的语言对实体进行描述然后通过编写程序创建对象后计算机才可以认识。比如想要让计算机认识洗衣机就需要
用户先要对现实中洗衣机实体进行抽象 – 即在人为思想层面对洗衣机进行认识洗衣机有什么属性有哪些功能即对洗衣机进行抽象认知的一个过程。经过上述步骤后在人的头脑中已经对洗衣机有了一个清晰的认识只不过此时计算机还不清楚想要让计算机识别人想象中的洗衣机就需要人通过某种面向对象的语言比如C、Java、Python等将洗衣机用类来进行描述并输入到计算机中。经过上述步骤后计算机中就有了一个洗衣机类但洗衣机类只是站在计算机的角度对洗衣机对象进行描述的通过洗衣机类可以实例化出一个个具体的洗衣机对象此时计算机才能清楚洗衣机是什么东西。接着用户就可以借助计算机中的洗衣机对象来模拟现实中的洗衣机实体了。
注意类是对某一实体对象来进行描述的描述该对象具有哪些属性哪些方法描述完之后就形成了一种新的自定义类型使用该自定义类型就可以实例化出具体的对象。 以上是我对C中类和对象相关知识的一些学习记录总结如有错误希望大家帮忙指正也欢迎大家给予建议和讨论谢谢