保定做网站建设,望野原文及翻译赏析,wordpress淘宝客 采集器,莱芜网站建设价格低目录 一、再探构造函数
1.基本定义以及用法
2.必须在初始化列表初始化的成员变量
3.成员变量声明位置的缺省值#xff08;C11#xff09;
4.成员变量初始化顺序
二、隐式类型转换
三、static成员
四、友元
五、内部类
六、匿名对象
七、日期类实现 一、再探构造函数…目录 一、再探构造函数
1.基本定义以及用法
2.必须在初始化列表初始化的成员变量
3.成员变量声明位置的缺省值C11
4.成员变量初始化顺序
二、隐式类型转换
三、static成员
四、友元
五、内部类
六、匿名对象
七、日期类实现 一、再探构造函数
1.基本定义以及用法 • 之前我们实现构造函数时初始化成员变量主要使用函数体内赋值构造函数初始化还有一种方 式就是初始化列表初始化列表的使用方式是以⼀个冒号开始接着是⼀个以逗号分隔的数据成 员列表每个成员变量后面跟⼀个放在括号中的初始值或表达式。 • 每个成员变量在初始化列表中只能出现⼀次语法理解上初始化列表可以认为是每个成员变量定义初始化的地方。 结合代码理解下 #includeiostream
using namespace std;class Date
{
public:Date(int year 1, int month 1, int day 1)// 初始化列表成员变量定义初始化的位置:_year(year),_month(month),_day(day 1) //每个成员变量只能出现一次括号里面可以放初始值或者表达式{}void Print() const{cout _year - _month - _day endl;}private:// 成员变量的声明int _year;int _month;int _day;
};
int main()
{// 类对象定义初始化Date d(2024, 7, 27);return 0;
} 2.必须在初始化列表初始化的成员变量 • 引用成员变量const成员变量没有默认构造的类类型变量必须放在初始化列表位置进行初始 化否则会编译报错。 #includeiostream
using namespace std;class Time
{
public:// Time类没有默认构造Time(int hour):_hour(hour){cout Time() endl;}
private:int _hour;
};class Date
{
public:Date(int x, int year 1, int month 1, int day 1)// 初始化列表成员变量定义初始化的位置:_year(year),_month(month),_day(day 1) //每个成员变量只能出现一次括号里面可以放初始值或者表达式,_t(1),_ref(x),_n(1)// 如果不初始化上面三个则会报错// error C2512: “Time”: 没有合适的默认构造函数可用// error C2530 : “Date::_ref” : 必须初始化引用// error C2789 : “Date::_n” : 必须初始化常量限定类型的对象{}void Print() const{cout _year - _month - _day endl;}private:// 成员变量的声明int _year;int _month;int _day;Time _t; // 没有默认构造的类类型对象int _ref; // 引用成员变量const int _n; //const成员变量
};
int main()
{int a 1;// 类对象定义初始化Date d(a,2024, 7, 27);return 0;
} 为什么会编译报错呢 • 因为初始化列表是类对象内成员变量定义初始化的地方就相当于主函数的int a 1;而引用和const变量只在定义时有一次也必须初始化的机会后续将不能改变引用对象和const变量的值。 • 初始化列表在没有手动初始化类类型时会自动调用类的默认构造来初始化如果没有默认构造当然就会报错 3.成员变量声明位置的缺省值C11 • C11支持在成员变量声明的位置给缺省值这个缺省值可以是值也可以是一个表达式主要是给没有显示在初始化列表初始化的成员使用的。 #includeiostream
using namespace std;class Date
{
public:Date(int year 1, int month 1, int day 1)// 成员变量定义初始化:_year(year), _month(month), _day(day){// ...}void Print() const{cout _year - _month - _day endl;}private:// 成员变量的声明// 这里没有初始化只是单纯的给缺省值int _year 0;int _month 0;int _day 0;
};
int main()
{// 类对象定义初始化Date d;d.Print();return 0;
} • 这里就要注意这里成员变量声明位置缺省值的优先级是小于构造函数参数缺省值的优先级的可以理解为构造函数没有将成员变量初始化好所以是优先使用参数位置的缺省值就会利用到声明位置的缺省值来初始化。 结合一下这几个运行结果来理解 • 尽量使用初始化列表初始化因为那些你不在初始化列表初始化的成员也会走初始化列表如果这个成员在声明位置给了缺省值初始化列表会用这个缺省值初始化。如果你没有给缺省值对于没有显示在初始化列表初始化的内置类型成员是否初始化取决于编译器C并没有规定。对于没有显示在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数如果没有默认构造会编译错误。 4.成员变量初始化顺序 • 初始化列表中按照成员变量在类中声明顺序进行初始化跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持⼀致。 二、隐式类型转换 • C支持内置类型隐式类型转换为类类型对象需要有相关内置类型为参数的构造函数 • 构造函数前面加explicit就不再支持隐式类型转换 内容不是很多这个主要是方便我们写代码下面主要结合代码来进行理解 #includeiostream
using namespace std;class A
{
public:// explicit A(int a1 1) 不想让这样的隐式类型转换发生就在函数前面加上explicit这个关键字就行了A(int a1 1):_a1(a1){_a2 _a1;}A(int a1, int a2):_a1(a1),_a2(a2){}void Print()const{cout _a1 _a2 endl;}private:int _a1;int _a2;
};class Stack
{
public:void Push(const A aa){// ...}
private:A arr[10];// 使用A的默认构造int _top 0; // 这样就不用写构造函数了哈哈
};int main()
{A aa1(0);// 正常定义初始化一个类对象aa1.Print();A aa2 2; // 这是啥// 隐式类型转换// 这里编译器会用 2 去调用构造函数得到一个临时对象再把临时对象拷贝构造给 aa2// 其实编译器还会优化编译器遇到连续构造拷贝构造-优化为直接构造// 有什么用呢方便传参就比如入栈aa2.Print();Stack s;// 正常情况下不用隐式类型转换入栈A aa3 3;s.Push(aa3);// 利用隐式类型转换入栈s.Push(3);// 3在括号里面调用构造函数生成一个临时对象再把临时对象入栈和上面那个入栈效果是相同的// 提醒一下临时对象具有常性为了不把权限放大Push函数里面A类型的参数一定要加const// 本来C是只支持单参数构造函数才能像上面那么写多参数是默认不支持的// 不过.........C11支持了// 这里是使用的演示要用到花括号{}大家注意用法就行A aa4 { 4,4 };s.Push(aa4);// 简化就是s.Push({ 4,4 });return 0;
} 大家用的时候注意写法就行了。 三、static成员 • 用static修饰的成员变量称之为静态成员变量静态成员变量⼀定要在类外进行初始化。 • 静态成员变量为所有类对象所共享不属于某个具体的对象不存在对象中存放在静态区。 • 用static修饰的成员函数称之为静态成员函数静态成员函数没有this指针。 • 静态成员函数中可以访问其他的静态成员但是不能访问非静态的因为没有this指针。 • 非静态的成员函数可以访问任意的静态成员变量和静态成员函数。 其实就是说静态的只可以访问静态的非静态的都可以访问 • 突破类域就可以访问静态成员可以通过类名::静态成员 或者 对象.静态成员 来访问静态成员变量和静态成员函数。前提是让这些成员公有 • 静态成员也是类的成员受public、protected、private 访问限定符的限制。 • 静态成员变量不能在声明位置给缺省值因为缺省值是构造函数初始化列表的静态成员变量不属于某个对象不走构造函数初始化列表。 #includeiostream
using namespace std;class A
{
public:// 在构造函数和析构函数里面调整_scount的值来记录类实例化出对象的个数A(){_scount;}A(const A aa){_scount;}~A(){--_scount;}static int GetScount()// 用static修饰的成员函数称之为静态成员函数静态成员函数没有this指针{// _a 2; // 静态成员函数没有this指针,不能访问非静态的成员变量return _scount;}int GetScount2()// 非静态的成员函数{int ret GetScount();// 可以访问静态成员函数return _scount; // 可以访问静态成员变量}private:// 类里面声明注意这里仅仅只是声明,这里不能给缺省值静态成员变量不属于某一个对象不走初始化列表static int _scount;// 用static修饰的成员变量称之为静态成员变量int a 1;
};// 类外面初始化
int A::_scount 0;int main()
{A aa1,aa2;A aa3(aa2);// 这里定义三个对象_scount会变成3// 静态成员变量为所有类对象所共享,可以通过类类型或者具体对象来调用cout A::GetScount() aa1.GetScount() aa2.GetScount() aa3.GetScount() endl;// 这里要注意非静态成员函数不能用A::GetScount2()来玩cout aa1.GetScount2() aa2.GetScount2() aa3.GetScount2() endl;// int x A::_scount; // error静态成员也是类的成员受public、protected、private 访问限定符的限制。return 0;
} 再总结一下 1. static修饰成员变量类内声明类外初始化不给缺省值不属于某一个对象为所有类对象所共享也可以用类名访问。 2. 静态成员变量也是成员变量仍然受访问限定符限制。 3. 静态成员函数由于没有this指针而不能访问非静态成员 那现在来道题
题目在牛客网求123...n_牛客题霸_牛客网 大家可以尝试一下自己利用刚刚学过的static成员自己尝试一下下面是我的过程供大家参考
// 从1到n要加n次怎么计数
// 可以建一个类构造一个关于这个类容量为n的数组会调用n次构造函数
// 用i记录下来构造函数调用的次数便能够计数
// 我们的任务就成了在构造函数里面实现求合class Sum
{
public:Sum(){_sum_i; //第一次构造sum1第二次2........_i;}static int GetSum(){return _sum;}
private:static int _i;static int _sum;
};int Sum::_i 1; // 这样_i就能从1开始随构造到加到n
int Sum::_sum 0;// sum来每次加上_i的值来求和class Solution {
public:int Sum_Solution(int n) {Sum arr[n];//利用数组调用n次构造函数return Sum::GetSum();}
};
四、友元
这里把友元关系当朋友关系就会很好理解 • 友元提供了⼀种突破类访问限定符封装的方式友元分为友元函数和友元类在函数声明或者类声明的前面加friend并且把友元声明放到⼀个类的里面。 • 外部友元函数可访问类的私有和保护成员友元函数仅仅是⼀种声明他不是类的成员函数。 • 友元函数可以在类定义的任何地方声明不受类访问限定符限制。 • ⼀个函数可以是多个类的友元函数。 下面这个代码只有友元函数友元类会放到后面几条的文字下面 #includeiostream
using namespace std;// 这里是前置声明因为A类里面的友元声明里面用到了B如果不声明编译器会不认识
class B;
class A
{// 友元声明friend void func(const A aa, const B bb);// 友元函数声明仅仅是一种声明它不是类的成员函数// 友元函数可以在类定义的任何地方声明不受类访问限定符限制在哪里都可以这里是为了美观一般都在最上面
private:int _a1 1;int _a2 2;
};class B
{// 友元声明friend void func(const A aa, const B bb);
private:int _b1 3;int _b2 4;
};// func在A类和B类中都进行了友元声明一个函数可以是多个类的友元函数
// 就可以理解为func是AB两个类的好朋友就可以访问它们的私有
void func(const A aa, const B bb)
{cout aa._a1 endl;cout bb._b1 endl;
}
int main()
{A aa;B bb;func(aa, bb);return 0;
} • 友元类中的成员函数都可以是另⼀个类的友元函数都可以访问另⼀个类中的私有和保护成员。 • 友元类的关系是单向的不具有交换性比如A类是B类的友元但是B类不是A类的友元。 • 友元类关系不能传递如果A是B的友元 B是C的友元但是A不是C的友元。 #includeiostream
using namespace std;
class A
{// 友元声明friend class B;//void func1(const B bb)//{// cout _a1 endl;// cout bb._b1 endl;// 友元关系是单向的不具有交换性B是A的友元类A不能访问B////}
private:int _a1 1;int _a2 2;
};class B
{
public:// B是A类的友元类就可以在B类里面访问A的私有void func1(const A aa){cout aa._a1 endl;cout _b1 endl;}// 友元类中的成员函数都可以是另一个类的友元函数都可以访问另一个类中的私有和保护成员。void func2(const A aa){cout aa._a2 endl;cout _b2 endl;}
private:int _b1 3;int _b2 4;
};int main()
{A aa;B bb;bb.func1(aa);bb.func2(aa);return 0;
} • 友元有时提供了便利。但是友元会增加耦合度破坏了封装所以友元不宜多用。 五、内部类 • 如果⼀个类定义在另⼀个类的内部这个内部类就叫做内部类。内部类是⼀个独立的类跟定义在全局相比他只是受外部类类域限制和访问限定符限制所以外部类定义的对象中不包含内部类。 • 内部类默认是外部类的友元类。 #includeiostream
using namespace std;class A
{
public:class B // B默认就是A的友元{public:void foo(const A a){cout _k endl;cout a._h endl; // 这里为什么可以直接使用_k而不能直接使用_h博主偶然发现这个问题时也很疑惑// 当外部类的成员变量不是类型名称、静态或枚举数时 内部类无法直接使用该成员变量// 这是我找到的资料以我们的水平暂时应该不用管其实用a._k程序也可以跑}private:int num 1;};private:static int _k;int _h 1;
};int A::_k 1;int main()
{cout sizeof(A) endl;// 这里结果表明外部类定义的对象中不包含内部类内部类是一个独立的类只是受到外部类访问限定符限制和类域限制A::B b; // 定义B类对象需要通过AA aa;b.foo(aa);// B对A的成员成功访问B是A的友元类 return 0;
} • 内部类本质也是⼀种封装当A类跟B类紧密关联A类实现出来主要就是给B类使用那么可以考虑把A类设计为B的内部类如果放到private/protected位置那么A类就是B类的专属内部类其其它地方都用不了。 还是我们上面static成员做过的那道题就可以通过内部类来稍微改一下
// 把_i和_sum作为了Solution的静态成员变量Sum类作为了Solution的专属内部类
class Solution {
public:int Sum_Solution(int n) {Sum arr[n];return Sum::GetSum();}class Sum{public:Sum(){_sum_i; _i;}static int GetSum(){return _sum;}};
private:static int _i;static int _sum;
};int Solution::_i 1;
int Solution::_sum 0;
六、匿名对象 • 用类型(实参) 定义出来的对象叫做匿名对象相比之前我们定义的 类型 对象名(实参) 定义出来的叫有名对象。 • 匿名对象生命周期只在当前一行⼀般临时定义⼀个对象当前用⼀下即可就可以定义匿名对象。 匿名对象用起来很爽可以让我们代码变简单看一下下面的代码就明白了
#includeiostream
using namespace std;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(1); // 但是它的生命周期只有这一行我们可以看到下一行他就会自动调用析构函数// 麻烦Solution st;cout st.Sum_Solution(10) endl;// 用匿名对象更方便cout Solution().Sum_Solution(10) endl;return 0;
}
解释一下运行结果 七、日期类实现
我直接把实现代码放出来用到的知识都是前面博主写过的会有必要的注释大家可以看一下
Date.h
// Date.h#pragma once#includeiostream
#includeassert.h
using namespace std;class Date
{friend ostream operator(ostream out, const Date d);friend istream operator(istream in, Date d);
public:// 检查日期是否合法bool CheckDate() const;// 构造函数Date(int year 1900, int month 1, int day 1);// 打印日期void Print() const;// 在类内实现的成员函数默认为内联// 为什么在类内实现因为这个函数频繁多次调用且较为短小作为内联是很好的选择// 得到当月天数int GetMonthDay(int year, int month) const{// 通过下标能得到平年月份相对的天数// 用static修饰是为了不让这个数组频繁建立和销毁因为要一直用static int MonthDay[13] { -1,31,28,31,30,31,30,31,31,30,31,30,31 };// 这里有个小细节month 2放在前面优先判断如果不是2月判断语句就会直接结束就不用再判断年份会提升一点效率if (month 2 ((year % 4 0 year % 100 ! 0) || (year % 400 0))){return 29;}return MonthDay[month];}// 日期的先后比较bool operator(const Date d) const;bool operator(const Date d) const;bool operator(const Date d) const;bool operator(const Date d) const;bool operator(const Date d) const;bool operator!(const Date d) const;// 日期的前几天后几天的基本运算Date operator(int day) const;Date operator(int day);Date operator-(int day) const;Date operator-(int day);// d1;Date operator(int);// d1;Date operator();// d1--;Date operator--(int);// --d1;Date operator--();// d1 - d2int operator-(const Date d) const;private:int _year;int _month;int _day;
};ostream operator(ostream out, const Date d);
istream operator(istream in, Date d); Date.cpp
// Date.cpp#include Date.h// 检查日期是否合法
bool Date::CheckDate() const
{if (_month 1 || _month 12 || _day GetMonthDay(_year, _month)){return false;}return true;
}// 构造函数 缺省值再头文件写了之后这里就不要再写了会重定义
Date::Date(int year, int month, int day):_year(year), _month(month), _day(day)
{assert((*this).CheckDate());//保证日期的合法性
}// 打印日期
void Date::Print() const
{cout *this endl;// 这里的流运算符是重载过的详细看这个文件的最下面
}// 日期的先后比较-可以优先实现等于和小于然后复用
bool Date::operator(const Date d) const
{if (_year d._year){return true;}else if (_year d._year){if (_month d._month){return true;}else if (_month d._month){return _day d._day;}}return false;
}bool Date::operator(const Date d) const
{return _year d._year _month d._month _day d._day;
}// 下面的都可以复用以上两个函数及互相之间复用
bool Date::operator(const Date d) const
{return *this d || *this d;
}
bool Date::operator(const Date d) const
{return !(*this d);
}bool Date::operator(const Date d) const
{return *this d || *this d;
}bool Date::operator!(const Date d) const
{return !(*this d);
}// 日期的前几天后几天的基本运算
Date Date::operator(int day) const
{Date tmp *this;tmp day;// 为什么是复用而不是复用因为可以返回引用有利于减少拷贝而复用则会增加拷贝下面的-和-同理return tmp;
}Date Date::operator(int day)
{// 考虑输入小于0的情况if (day 0){return *this - (-day);}_day day;while (_day GetMonthDay(_year, _month)){_day - GetMonthDay(_year, _month);_month;if (_month 13){_year;_month 1;}}return *this;
}Date Date::operator-(int day) const
{Date tmp *this;tmp - day;return tmp;
}Date Date::operator-(int day)
{if (day 0){return *this (-day);}_day - day;while (_day 1){--_month;if (_month 0){--_year;_month 12;}_day GetMonthDay(_year, _month);// 这行代码放到下面就不用检查_month了}return *this;
}// d1;
Date Date::operator(int)
{Date tmp *this;*this 1;return tmp;
}// d1;
Date Date::operator()
{*this 1;return *this;
}// d1--;
Date Date::operator--(int)
{Date tmp *this;*this - 1;return tmp;
}// --d1;
Date Date::operator--()
{*this - 1;return *this;
}// d1 - d2
int Date::operator-(const Date d) const
{// CPU的运行是很快的一般每秒几十亿次所以如果不是特殊需求我们可以通过计数来算日期差值int flag 1;Date max *this;Date min d;if (*this d){flag -1;max d;min *this;}int count 0;while (max ! min){min;count;}return count * flag;
}ostream operator(ostream out, const Date d)
{out d._year 年 d._month 月 d._day 日 endl;return out;
}
istream operator(istream in, Date d)
{while (1){cout 请依次输入年月日:_; //这里不写endl是为了不换行直接在当行输入in d._year d._month d._day;if (!d.CheckDate()){cout 输入日期不合法请重新输入 endl;}else{break;}}return in;
}
Test.cpp
// Test.cpp#include Date.hvoid DateTest1()
{Date d1(2024, 7, 27);Date d2 d1 100;d1.Print();d2.Print();d1 1000;d2 d1 1000;d1.Print();d2.Print();}void DateTest2()
{Date d1(2024, 7, 27);d1 - 3000;d1.Print();Date ret1 d1;ret1.Print();d1.Print();Date d2(2024, 7, 27);Date ret2 d2;ret2.Print();d2.Print();
}void DateTest3()
{Date d1(2024, 7, 27);d1 -100;d1.Print();d1 - -100;d1.Print();
}void DateTest4()
{Date d1(2039, 12, 1);Date d2(2024, 7, 27);cout d1 - d2 endl;cout d2 - d1 endl;}void DateTest5()
{Date d1, d2;cin d1 d2;cout d1 d2;cout d1 - d2 endl;}int main()
{//DateTest1();//DateTest2();//DateTest3();//DateTest4();//DateTest5();return 0;
} 终于写完了......
类和对象完结撒花
ヾ(~□~;)