类和对象(3)

再谈构造函数

class Date
{
public:
 Date(int year, int month, int day)
 {
     _year = year;
     _month = month;
     _day = day;
 }
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;
};

注意

1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

2. 类中包含以下成员,必须放在初始化列表位置进行初始化: 引用成员变量

const成员变量

自定义类型成员(且该类没有默认构造函数时)

3.尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量, 一定会先使用初始化列表初始化

4.成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

下面这段代码:

尽管初始化列表尝试首先初始化_a1然后初始化_a2,但实际的初始化顺序是由成员变量在类定义中的声明顺序决定的。因此,_a2会首先被初始化(使用未初始化的_a1),然后_a1才会被初始化为传入的参数a

_a2初始化时,_a1是未定义的(即处于未初始化状态),但C++标准并不要求在这种情况下编译器产生错误或警告。实际上,许多编译器会允许这种用法,但_a2会使用_a1的当前(未定义)值进行初始化,这通常是一个未定义行为(Undefined Behavior, UB),但在实践中,它可能会表现为使用某个随机值。

explicit关键字

构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值 的构造函数,还具有类型转换的作用。

用explicit修饰构造函数,将会禁止构造函数的隐式转换。

static成员

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用 static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化

1.静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区

2. 静态成员变量必须在类外定义定义时不添加static关键字,类中只是声明

3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问

4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员

5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制

题目:

class A {  
public:  
    A() { ++_scount; } // 构造函数,每当创建一个A类的新对象时,都会增加_scount的值
    A(const A& t) { ++_scount; } // 拷贝构造函数,当通过拷贝另一个A类对象来创建新对象时,也会增加_scount的值
    ~A() { --_scount; } // 析构函数,每当一个A类的对象被销毁时,都会减少_scount的值
    static int GetACount() { return _scount; } // 静态成员函数,返回当前A类对象的数量
private:  
    static int _scount; // 静态私有成员,用于跟踪A类对象的数量(声明)
};  
  
int A::_scount = 0; // 在类定义外部初始化静态成员_scount,初始值为0  
  
void TestA() {  
    cout << A::GetACount() << endl; // 输出当前A类对象的数量,此时应该为0,因为还没有创建任何A类的对象
    A a1, a2; // 创建两个A类的对象a1和a2,此时A类对象的数量应该增加到2
    A a3(a1); // 通过拷贝a1来创建一个新的A类对象a3,此时A类对象的数量应该增加到3
    cout << A::GetACount() << endl; // 再次输出当前A类对象的数量,此时应该为3
}

1.静态成员函数可以调用非静态成员函数吗?

不能。静态成员函数属于类本身,而非类的某个特定对象。非静态成员函数则依赖于类的特定实例,由于静态成员函数没有this指针,因此它无法访问类的非静态成员,因为这些非静态成员需要this指针来访问。

2. 非静态成员函数可以调用类的静态成员函数吗? 

可以。静态成员函数属于类本身,而非类的某个特定对象。非静态成员函数则依赖于类的特定实例,所以它们可以在没有任何对象实例的情况下被调用。非静态成员函数可以通过类名直接调用静态成员函数,也可以通过this指针调用,尽管通过this指针调用并不是必要的,因为静态成员函数不属于任何特定对象实例。

友元

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以 友元不宜多用。 友元分为:友元函数和友元类

友元函数

现在尝试去重载operator,然后发现没办法将operator重载成成员函数。

因为cout的输出流对象隐含的this指针抢占第一个参数的位置,this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将operator重载成 全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator>>同理。

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在 类的内部声明,声明时需要加friend关键字。

friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);

友元函数可访问类的私有和保护成员,但不是类的成员函数

友元函数不能用const修饰

友元函数可以在类定义的任何地方声明,不受类访问限定符限制

一个函数可以是多个类的友元函数

友元函数的调用与普通函数的调用原理相同

友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

友元关系是单向的,不具有交换性。

比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接 访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。

友元关系不能传递 如果C是B的友元, B是A的友元,则不能说明C是A的友元。

友元关系不能继承。

内部类

概念:如果一个类定义在另一个类的内部,这个类就叫做内部类。

内部类是一个独立的类, 它不属于外部类,更不能通过外部类的对象去访问内部类的成员

外部类对内部类没有任何优越的访问权限。

注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访 问外部类中的所有成员。但是外部类不是内部类的友元。

特性: 1. 内部类可以定义在外部类的public、protected、private都是可以的。

2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。

3. sizeof(外部类)=外部类,和内部类没有任何关系。

匿名对象

int main()
{
 A aa1;
 // 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
 //A aa1();
 // 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
 // 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
 A();
 A aa2(2);
 // 匿名对象在这样场景下就很好用,当然还有一些其他使用场景,这个我们以后遇到了再说
 Solution().Sum_Solution(10);
 return 0;
}

拷贝对象时的一些编译器优化

在传参和传返回值的过程中,编译器会做一些优化,减少一些对象的拷贝。

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;
}

再次理解类和对象

类是对某一类实体(对象)来进行描述的,描述该对象具有那些属性,那些方法描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化具体的对象。

题目 

求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com)

求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

class Solution {
public:
    int Sum_Solution(int n) {
        n && (n += Sum_Solution(n - 1));
        return n;
    }
};

计算日期到天数转换_牛客题霸_牛客网 (nowcoder.com)

根据输入的日期,计算是这一年的第几天。保证年份为4位数且日期合法。

#include <iostream>
using namespace std;
int main() {
    int year, mon, day;
    int data[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    while (cin >> year >> mon >> day) {
        int sum;
        for (int i = 0; i < mon - 1; i++) {
            sum = sum + data[i];
        }
        if ((year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)) && mon > 2)
            sum = sum + 1 + day;
        else
            sum = sum + day;
        cout << sum << endl;

    }
    return 0;
}

日期差值_牛客题霸_牛客网 (nowcoder.com)

#include <iostream>  // 引入输入输出流库  
#include <string>    // 引入字符串处理库  
  
using namespace std; // 使用标准命名空间,避免每次调用时都加std::前缀  
  
// 函数用于判断是否是闰年  
bool isLeapYear(int year) {    
    return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);    
}    
  
// 函数用于计算给定年份中某个月的天数  
int daysOfMonth(int year, int month) {    
    int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};    
    if (month == 2 && isLeapYear(year)) {    
        return 29;  // 如果是闰年的2月,则返回29天  
    }    
    return days[month - 1];  // 返回其他月份的天数  
}    
  
// 函数用于计算两个日期之间的天数差  
int calculateDaysBetweenDates(string date1, string date2) {    
    int year1, month1, day1;    
    int year2, month2, day2;    
    
    // 解析日期字符串  
    year1 = stoi(date1.substr(0, 4));  // 提取第一个日期的年份  
    month1 = stoi(date1.substr(4, 2)); // 提取月份  
    day1 = stoi(date1.substr(6, 2));   // 提取日  
    year2 = stoi(date2.substr(0, 4));  // 提取第二个日期的年份  
    month2 = stoi(date2.substr(4, 2)); // 提取月份  
    day2 = stoi(date2.substr(6, 2));   // 提取日  
    
    // 计算两个日期之间的天数差  
    int daysBetween = 0;    
    while (year1 != year2 || month1 != month2 || day1 != day2) {    
        day1++;  // 递增天数  
        if (day1 > daysOfMonth(year1, month1)) {  // 如果天数超出当前月份  
            day1 = 1;  // 重置天数  
            month1++;  // 递增月份  
            if (month1 > 12) {  // 如果月份超出12月  
                month1 = 1;  // 重置月份为1月  
                year1++;  // 递增年份  
            }    
        }    
        daysBetween++;  // 累加天数差  
    }    
    
    // 下面的条件几乎永远不会为真(除非两个日期完全相同),且逻辑上不需要加1  
    // 如果想要处理“连续日期视为两天”的特殊情况,应该在外部逻辑中处理  
    if (year1 == year2 && month1 == month2 && day1 == day2) {    
        daysBetween += (daysBetween == 1 ? 1 : 0);  // 这里是多余的,因为daysBetween几乎不会为1  
    }    
    
    return daysBetween;  // 返回天数差  
}    
    
int main() {    
    string date1, date2;    
    while (cin >> date1 >> date2) {  // 循环读取输入直到结束  
        cout << calculateDaysBetweenDates(date1, date2) << endl;  // 输出天数差  
    }    
    return 0;  // 程序正常结束  
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值