类和对象(下)

1.初始化列表

先看一段代码

class Stack
{
public:
	Stack()
	{
		cout << "Stack()" << endl;
	}
private:
	int size;
};

class A
{
public:
private:
	Stack s;
	int a;
};

int main()
{
	A tmp;
	return 0;
}

这段代码没问题,A没有显示实现构造函数,那么就调用编译器默认的构造函数,内置类型不管,自定义类型调用它的默认构造函数,所以这个代码没问题
所谓默认构造函数,就是不用传参就可以使用的构造函数,具体有三种,第一个是,我们实现了一个没有参数的构造函数,第二是全缺省的构造函数,第三就是我们没有实现构造函数,由编译器默认实现的构造函数,也是没有参数的
所以如果我们实现了一个Stack的构造函数,有参数,而且不是全缺省。那么就会出问题

class Stack
{
public:
	Stack(int a)
	{
		cout << "Stack()" << endl;
	}
private:
	int size;
};

class A
{
public:
private:
	Stack s;
	int a;
};

int main()
{
	A tmp;
	return 0;
}

在这里插入图片描述
所以这样的话,A的构造函数只能由我们显示实现
但是怎么实现呢

	A()
	{
		s(2);
		a = 0;
	}

这样实现吗,这样实现显然是不行的,因为栈的构造函数的调用是在定义的时候,而在函数体内部就已经说明了这个栈已经定义了,但是栈又没有默认构造函数,所以肯定会出问题。所以我们必须找一个定义的地方
所以我们引入初始化列表,这个东西就是类的成员变量定义的地方

	A()
		:s(2)
		,a(2)
	{
		;
	}

显然,初始化列表是在函数体的前面的,函数名的后面
还可以这样写

	A(int a)
		:s(a)
		,a(a)
	{
		;
	}

所以初始化列表,冒号开始,逗号分开,括号对于内置类型就是直接赋值。对于自定义就是调用参数调用构造函数
还有一些成员变量,比如引用,还有const参与的,这些都必须在定义的时候赋值,而且不能改变了,所以只能在初始化列表初始化


class A
{
public:
	A(int a)
		:s(a)
		,a1(a)
		,a2(a)
		,a3(a)
	{
		;
	}
private:
	Stack s;
	int a1;
	const int a2;
	int& a3;
};
class Stack
{
public:
	Stack()
	{
		cout << "Stack()" << endl;
	}
private:
	int size;
};

class A
{
public:
	A(int a)
		:a2(a)
		,a3(a)
	{
		cout << a1 << endl;
	}
private:
	Stack s;
	int a1;
	const int a2;
	int& a3;
};

在这里插入图片描述

对于有默认构造的自定义类型成员变量,没有在初始化列表也没事,会自动走的,会自动走初始化列表,走的是默认构造的,对于内置类型没有在初始化列表,也会走初始化列表,只不过可能是随机值,也可能是0,这个还是看编译器

class A
{
public:
	A(int a)
		:a2((int*)malloc(40))
	{
		cout << a1 << endl;
	}
private:
	Stack s;
	int a1;
	int* a2;
};

甚至对于指针类型还可以这样使用
这里我们建议以后都要用初始化列表去初始化
来看一道题

class A {
public:
    A(int a)
       :_a1(a)
       ,_a2(_a1)
   {}
    
    void Print() {
        cout<<_a1<<" "<<_a2<<endl;
   }
private:
    int _a2;
    int _a1;
};
int main() {
    A aa(1);
    aa.Print();
}
//A. 输出1  1 
//B.程序崩溃
//C.编译不通过
//D.输出1  随机值

在这里插入图片描述
答案是选D,为什么呢,按照常理来说应该是选A的,但是为什么呢
这里要注意一下了,初始化列表的顺序是和成员变量的顺序一样的,因为成员变量a2在前面,所以先执行_a2(_a1),所以选D

class Stack
{
public:
	Stack(int a)
	{
		cout << "Stack()" << endl;
	}
private:
	int size;
};

class A
{
public:
	A(int a)
		:s(2)
		
	{
		cout << a1 << endl;
		cout << a2 << endl;
		cout << a3 << endl;
	}
private:
	Stack s;
	int a1=2;
	const int a2=2;
	int& a3=a1;
};
int main()
{
	A a(4);
	return 0;
}

在这里插入图片描述

class Stack
{
public:
	Stack(int a)
	{
		cout << "Stack()" << endl;
	}
private:
	int size;
};

class A
{
public:
	A(int a)
		:a2(a)
		,a3(a)
		,s(2)
		
	{
		cout << a1 << endl;
		cout << a2 << endl;
		cout << a3 << endl;
	}
private:
	Stack s;
	int a1=2;
	const int a2=2;
	int& a3=a1;
};
int main()
{
	A a(4);
	return 0;
}

在这里插入图片描述
通过这两个代码我们可以看出,我们还可以与成员变量的默认值结合,注意,自定义类型是不可以设置默认值的
如果一个成员变量没有在初始化列表,会去走默认值,这个默认值就相当于在初始化列表了
如果一个成员在初始化列表,那么就会直接走初始化列表,不会走默认值了
这里对于const那种类型,同时有默认值和初始化列表是不会冲突的,因为有初始化列表,默认值就没用,没有初始化列表,默认值就相当于在初始化列表了
最后再走函数体
说白了,初始化列表就是成员变量定义的地方
还要注意每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

2. explicit关键字

在讲这个之前,我们先讲一下类型转换

class Stack
{
public:
	Stack(int a)
	{
		cout << "Stack()" << endl;
	}
	Stack(Stack& s)
	{
		cout << "	Stack(Stack& s)" << endl;
	}
private:
	int size;
};

int main()
{
	Stack s1(2);
	Stack s2 = 2;
	Stack s3 = 23.23;
	return 0;
}

在这里插入图片描述

在这个代码中,第一个定义对象的方法还可以理解,但是第二个呢
第二个是这样的,先用2来构造一个对象x,2传给a来构造,然后再将x拷贝构造给s1,所以过程是先构造,在拷贝构造,但是呢,对于这个过程,编译器进行了优化,可以直接用2来构造s1,所以只打印了一个,但是底层的逻辑我们要知道
第三个就是将23.23这个小数传给int型的a,会发生类型转换,会截断小数点后面的内容,然后在构造,在拷贝构造
所以这样就很方便了,利用隐式类型转换,以后才可能知道
有一些隐式类型转换我们要小心一点,尤其是与const相关的引用和指针,最有可能发生权限的放大

int main()
{
	int i = 0;
	double& j = i;
	return 0;
}

比如说这个代码就不行,这里还会发生隐式类型转换,前面用括号括起来,像这样(double)这叫强制类型转换,会先创建一个double变量x,用来接受i隐式类型转换而来的东西,然后在引用给j,但是呢,临时变量x具有常性,这样会发生权限的缩小,所以有错,应该这样

int main()
{
	int i = 0;
	const double& j = i;
	return 0;
}
int main()
{
	const int a = 0;
	int* b = &a;
	return 0;
}

这个因为a是const,所以a的地址默认为const*int,所以这样又会权限的放大,所以不行
所以一般建议给引用加上const,不修改的话,因为权限的缩小是不会出问题的

int main()
{
	const int a = 0;
	const int* b = &a;
	return 0;
}

上面那个代码针对的是单个参数的自定义类型的隐式类型转化
拥有多参数的构造函数的类也可以隐式类型转化

class Stack
{
public:
	Stack(int a=3,int b=4)
	{
		cout << "Stack(int a=3,int b=4)" << endl;
	}
	Stack(const Stack& s)
	{
		cout << "Stack(Stack& s)" << endl;
	}
private:
	int size;
};

int main()
{
	Stack s1(2, 3);
	Stack s2 = { 2,3 };
	return 0;
}

这个嘛,就是多参数的隐式类型转换,把2和3分别传给a和b,然后后面操作就是一样的了
然后就是explicit关键字,把这个关键字放在构造函数的前面,就会禁止发生隐式类型转换

class Stack
{
public:
	explicit Stack(int a = 3, int b = 4)
	{
		cout << "Stack(int a=3,int b=4)" << endl;
	}
	Stack(const Stack& s)
	{
		cout << "Stack(Stack& s)" << endl;
	}
private:
	int size;
};

int main()
{
	Stack s1(2, 3);
	Stack s2 = { 2,3 };
	return 0;
}

在这里插入图片描述

3.static成员

static可以修饰成员变量,修饰的成员变量必须要在类的外面进行定义,就相当于函数的定义与声明一样的

class Stack
{
public:
	Stack()
	{
		cout << "Stack()" << endl;
	}
	Stack(const Stack& s)
	{
		cout << "Stack(Stack& s)" << endl;
	}
private:
	int i;
	static int count;
};
int Stack::count = 0;

int main()
{
	cout << sizeof(Stack) << endl;
	return 0;
}

在这里插入图片描述
静态成员是存放在静态区的,所以不占类的大小

class Stack
{
public:
	int Get()
	{
		return _count;
	}
	Stack()
	{
		++_count;
		cout << "Stack()" << endl;
	}
	Stack(const Stack& s)
	{
		cout << "Stack(Stack& s)" << endl;
	}
private:
	int _i;
	static int _count;
};
int Stack::_count = 0;

int main()
{
	Stack s1;
	Stack s2;
	cout << s1.Get() << endl;
	cout << s2.Get() << endl;
	return 0;
}

在这里插入图片描述
而且这个静态成员变量是所有对象共同拥有的,意思是所有对象的_count都是一样的
如果静态成员不是私有的,那么静态成员的访问可以有两种方式,第一种就是Stack::,第二种就是s1.
这样的话,我们就可以来计算计算机到底创建了多少个类

	Stack()
	{
		++_count;
		cout << "Stack()" << endl;
	}

这样的话,得到count就可以了

	~Stack()
	{
		--_count;
		cout << "~Stack()" << endl;
	}

如果再加上这个的话,就可以计算计算机中到底有多少个有效的没有被销毁的对象
在这里插入图片描述
然后就是注意,static修饰函数就是静态函数,静态函数是不能访问非静态成员变量的
还有就是静态函数可以访问非静态成员函数,非静态成员函数不能访问静态函数,可以访问静态变量
静态函数的访问也是有两种,因为静态函数只能访问静态变量,所以可以随时访问
这一切的原因都是因为静态函数没有隐藏的this指针

4.友元

友元函数我们前面已介绍过,这里粗略介绍一下
友元函数声明在类里面,需要在前面加上friend,实现在类的外面
友元函数可以访问类的私有的成员变量
友元函数的调用与普通函数的调用原理相同
友元函数不能用const修饰
好的我们接下来讲一下友元类
所谓友元类,就是一个类是另一个类的友元,是另一个类的朋友,那么这个类就可以访问另一个类的成员变量

class Stack1
{
	friend class Stack2;
public:
	Stack1()
	{
		cout << "Stack1()" << endl;
	}
	Stack1(const Stack1& s)
	{
		cout << "Stack1(Stack1& s)" << endl;
	}
private:
	int _i;
};

class Stack2
{
public:
	Stack2()
	{
		Stack1 s1;
		s1._i;
		cout << "Stack2()" << endl;
	}
	Stack2(const Stack2& s)
	{
		cout << "Stack2(Stack2& s)" << endl;
	}
private:
	int _i;
};

这里Stack2是Stack1的友元,的朋友,那么在实现Stack2的某些函数时,定义Stack1的变量时,就可以访问Stack的成员变量了,而Stack1这个类中就不可以访问Stack2的成员变量,因为我把你当朋友,你可能不会把我当朋友
还有就是友元关系不能传递
如果C是B的友元, B是A的友元,则不能说明C时A的友元。
至于两个类互为友元这种情况,比较复杂,我就不介绍了

5.内部类

所谓内部类就是在一个类里面在定义一个类,这就叫做内部类

class Stack1
{
public:
	Stack1()
	{
		cout << "Stack1()" << endl;
	}
	Stack1(const Stack1& s)
	{
		cout << "Stack1(Stack1& s)" << endl;
	}
	class Stack2
	{
	public:
		Stack2()
		{

		}
	private:
		int _i;
	};
private:
	int _i;
};

int main()
{
	cout << sizeof(Stack1) << endl;
	return 0;
}

在这里插入图片描述
你看就算在类里面定义一个类,这个类的大小也不会增加,关键原因是比如在建立一个Stack1的对象的时候,并不会建立一个Stack2的对象,所以大小就那么大

class Stack1
{
public:
	Stack1()
	{
		cout << "Stack1()" << endl;
	}
	Stack1(const Stack1& s)
	{
		cout << "Stack1(Stack1& s)" << endl;
	}
private:
	int _i;
};

class Stack2
{
public:
	Stack2()
	{

	}
private:
	int _i;
};

int main()
{
	cout << sizeof(Stack1) << endl;
	return 0;
}

所以说这两段代码并没有什么区别,在类里面定义一个类,与在类外面定义一个类,两个并没有什么区别,区别就是定义在类里面的那个类的类域受到了限制,要访问的话,要说明在哪个域

int main()
{
	cout << sizeof(Stack1) << endl;
	Stack1 s1;
	Stack1::Stack2 s2;
	return 0;
}

比如这样就可以创建两个类了,这两个类之间没什么关系
里面那个类叫做内部类,外面的叫做外部类
还有就是要注意内部类天生就是外部类的友元,而外部类不是内部类的友元,所以内部类可以访问外部类的成员变量

class Stack1
{
public:
	Stack1()
	{
		cout << "Stack1()" << endl;
	}
	Stack1(const Stack1& s)
	{
		cout << "Stack1(Stack1& s)" << endl;
	}
	class Stack2
	{
	public:
		Stack2()
		{
			Stack1 s1;
			s1._i1;
		}
	private:
		int _i2;
	};
private:
	int _i1;
};

还有一个点就是内部类可以访问外部类的静态成员变量,而且是不需要建立一个对象就可以访问,是可以直接访问的

class Stack1
{
	friend class Stack2;
public:
	Stack1()
	{
		cout << "Stack1()" << endl;
	}
	Stack1(const Stack1& s)
	{
		cout << "Stack1(Stack1& s)" << endl;
	}
	class Stack2
	{
	public:
		Stack2()
		{
			cout << ss << endl;
		}
	private:
		int _i2;
	};
private:
	int _i1;
	static int ss;
};
int Stack1::ss = 0;

6.匿名对象

所谓匿名对象就是创建一个对象,这个对象是没有名字的

class Stack1
{
	friend class Stack2;
public:
	Stack1()
	{
		cout << "Stack1()" << endl;
	}
	~Stack1()
	{
		cout << "~Stack1()" << endl;
	}
private:
	int _i1;
};

int main()
{
	Stack1 s1;
	//Stack1 s1();//不能这样定义
	Stack1();
	cout << endl;
	return 0;
}

在这里插入图片描述
由这个我们可以看出Stack1();这一行就是创建了一个没有名字的对象,然后这个对象的生命周期只有这一行,过了这一行就析构了
有什么用呢,当你只想访问一次的时候,这个就有用
比如

Solution().Sum_Solution(10);

创建的时候直接就去访问了
以后遇到了再说

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

有一个优化我们已经讲过了,就是上面的隐式类型转换时,构造加拷贝构造就会转换为直接构造
第二个

class Stack
{
public:
	Stack()
	{
		_i = 0;
		cout << "Stack()" << endl;
	}
	Stack(const Stack&s)
	{
		_i = s._i;
		cout << "Stack()" << endl;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
	}
private:
	int _i;
};

Stack f()
{
	Stack s;
	return s;
}

int main()
{
	Stack s1 = f();
	return 0;
}

在这里插入图片描述

这里是构造,拷贝构造,再加上拷贝构造,一般的编译器会把后面的两个拷贝构造优化为一个拷贝构造,但我的VS2022优化的比较厉害,只有一个构造
这些优化知道就可以了,并不用太了解

8.练习题

在这里插入图片描述
对于这道题,我们直接先定义一个类的数组,这个数组有n个的话,就相当于走了构造函数n次,在每次都相加i,就可以了,为保证每次值和循环的一样,我们要设置静态成员变量,还有要访问静态变量,就要建立一个静态函数,因为建立非静态函数的话,还要多创建变量

#include <linux/limits.h>
class A
{
public:
    A()
    {
        sum+=i;
        ++i;
    }
    static int Get()
    {
        return sum;
    }
private:
    static int sum;
    static int i;
};
int A::sum=0;
int A::i=1;

class Solution {
public:
    int Sum_Solution(int n) {
        A arr[n];
        return A::Get();
    }
};

方法二

class Solution {
public:
    class A
    {
        public:
        A()
        {
            sum+=i;
            i++;
        }
    };
    int Sum_Solution(int n) {
        A arr[n];
        return sum;
    }
    private:
    static int sum;
    static int i;
};
int Solution::sum=0;
int Solution::i=1;

方法二就是定义内部类
然后就是内部类可以访问外部类的静态变量
在这里插入图片描述

#include <iostream>
using namespace std;

int main() {
    int arr[]={0,31,59,90,120,151,181,212,243,273,304,334,365};
    //先定义一个数组来存储到某个月的所有天数
    int year=0;
    int month=0;
    int day=0;
    cin>>year>>month>>day;
    int sum=arr[month-1]+day;
    if(month>2&&((year%4==0&&year%100!=0)||year%400==0))
    {
        ++sum;
    }
    cout<<sum;
    return 0;
}

在这里插入图片描述

#include <climits>
#include <iostream>
using namespace std;

class Date
{
    public:
    int GetDay(int year,int month)
    {
        int arr[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
        if(month==2&&((year%4==0&&year%100!=0)||(year%400==0)))
        {
            return 29;
        }
        return arr[month];
    }
    Date(int year,int month,int day)
    {
        _year=year;
        _month=month;
        _day=day;
    }
    Date(Date&d)
    {
        _year=d._year;
        _month=d._month;
        _day=d._day;
    }
    bool operator==(Date&d)
    {
        return _year==d._year&&
        _month==d._month&&
        _day==d._day;
    }
    Date&operator++()
    {
        _day++;
        if(_day>GetDay(_year, _month))
        {
            _month++;
            _day=1;
            if(_month>12)
            {
                _month=1;
                _year++;
            }
        }
        return (*this);
    }
    bool operator>(Date&d)
    {
        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;
    }
    void operator=(Date&d)
    {
        _year=d._year;
        _month=d._month;
        _day=d._day;
    }
    private:
    int _year;
    int _month;
    int _day;
};
int main() {
    int a, b;
    while (cin >> a >> b) { // 注意 while 处理多个 case
        int year1=a/10000;
        int month1=(a%10000)/100;
        int day1=a%100;
        int year2=b/10000;
        int month2=(b%10000)/100;
        int day2=b%100;
        //定义两个日期类
        Date time1(year1,month1,day1);
        Date time2(year2,month2,day2);
        Date max=time1;
        Date min=time2;
        if(min>max)
        {
            max=time2;
            min=time1;
        }
        int gap=0;
        while (!(min == max))
        {
            ++min;
            ++gap;
        }
        ++gap;
        cout<<gap;
    }
    return 0;

}
// 64 位输出请用 printf("%lld")

在这里插入图片描述

#include <iostream>
using namespace std;

int main() {
    int a, b;
    while (cin >> a >> b) { // 注意 while 处理多个 case
        int year=a;
        int month=0;
        int day=0;
        int arr[]={0,31,59,90,120,151,181,212,243,273,304,334,365};
        if((year%4==0&&year%100!=0)||year%400==0)
        {
            for(int i=2;i<13;i++)
            {
                arr[i]++;
            }
        }
        for(int i=1;i<13;i++)
        {
            if(b<arr[i])
            {
                month=i;
                day=b-arr[i-1];
                break;
            }
        }
        printf("%d-%02d-%02d",year,month,day);
    }
}
// 64 位输出请用 printf("%lld")

在这里插入图片描述

#include <iostream>
using namespace std;
int GetDay(int year,int month)
    {
        int arr[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
        if(month==2&&((year%4==0&&year%100!=0)||(year%400==0)))
        {
            return 29;
        }
        return arr[month];
    }

int main() {
    int year,month,day,count;
    int n;
    cin>>n;
    while(n--)
    {
        cin>>year>>month>>day>>count;
        while(count--)
        {
            day++;
        if(day>GetDay(year, month))
        {
            month++;
            day=1;
            if(month>12)
            {
                month=1;
                year++;
            }
        }
        }
        printf("%d-%02d-%02d\n",year,month,day);
    }
}

总结

类和对象就到此结束啦!!!!

  • 17
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值