c++初阶

目录

一.命名空间

1.命名空间冲突

2.namespace定义命名空间

3.using展开命名空间

a.全局域和展开的命名空间域冲突

b.展开的命名空间域和局部域不发生冲突

c.同时展开两个命名空间域发生冲突

5.using指定展开某一个命名空间

二.输入输出

三.缺省参数

1.全缺省参数

2.半缺省参数

三.函数重载

四.引用        

五.内联(inline)

宏的优缺点

六.auto

七.c++11  for循环

八.nullptr

九.类

1.定义

2.实例化

3.类对象的大小

十.类访问限定符及封装

访问限定符说明:

结构体内存对齐规则

十一.this指针

特性:

1. this指针存在哪里?

2. this指针可以为空吗?

十二.类的默认成员函数 

1.构造函数

默认构造函数(三者只能存在一个)

2.析构函数

3.拷贝构造

4.再探构造函数(初始化列表)

十三.赋值运算符重载

1.运算符重载

2.赋值运算符重载

3.前置++和后置++重载

日期类代码实现

十四.const 成员

取地址及const取地址操作符重载

十五.static(类的静态成员)

实现一个类,计算程序中创建出了多少个类对象

JZ64 求1+2+3+...+n

 十六.类型转换

十七.友元(友元函数和友元类)

十八.内部类

十九.匿名对象

二十.对象拷⻉时的编译器优化


一.命名空间

1.命名空间冲突

#include<stdio.h>
#include<stdlib.h>//rand头文件
int rand = 10;
int main()
{
	printf("%d", rand);//报错:error C2365: “rand”: 重定义;以前的定义是“函数”
	return 0;
}

这是由于头文件包含的函数rand()和全局域的rand变量名冲突,为了解决此问题,引入C++的“命名空间”

2.namespace定义命名空间

#include<stdio.h>
#include<stdlib.h>
namespace num //命名空间域
{
	int rand = 10;
}
int main()
{
	printf("%p\n", rand);//访问头文件中rand()的地址
	printf("%d\n", num::rand);//输出命名空间域rand的值
	return 0;
}

命名空间只能用“域名::变量名”的格式访问

---------若无命名空间查找符,则查找顺序:a.局部域        b.全局域

namespace 可嵌套使用

命名空间的合并: 在不同的文件中定义同一个命名空间,编译器会将它们合并在一起。可以在不同的文件中扩展同一个命名空间

3.using展开命名空间

a.全局域和展开的命名空间域冲突

#include<stdio.h>
#include<stdlib.h>
namespace num
{
	int rand = 10;
}
using namespace num;
int main()
{
	printf("%p\n", rand);//命名空间展开与全局域冲突    
                        //报错:error C2872: “rand”: 不明确的符号
	printf("%d\n", num::rand);
	return 0;
}

b.展开的命名空间域和局部域不发生冲突

#include<stdio.h>
namespace num
{
	int a = 10;
}
using namespace num;
int main()
{
	int a = 1;
	printf("%d\n", a);//1
	return 0;
}

c.同时展开两个命名空间域发生冲突

#include<stdio.h>
namespace num
{
	int a = 10;
}
namespace dis
{
	int a = 20;
 }
using namespace num;
using namespace dis;
int main()
{
	
	printf("%d\n", a);//报错:error C2872: “a”: 不明确的符号
	return 0;
}

总结:当命名空间域展开时,若存在全局域与命名空间中的(函数名/变量名/类型)相同时,冲突。

但当只有展开的命名空间域和局部域中的(函数名/变量名/类型)相同时,不发生冲突。

(即:相当于命名空间域展开成为了全局域的一部分)

4.命名空间对应变量名后续可不再指定空间

5.using指定展开某一个命名空间

"using 命名空间名::变量名"

#include<stdio.h>
namespace num1
{
	int a = 1;
	int b = 2;
}
using num1::a;
int main()
{
	
	printf("%d\n", a);//1
	printf("%d\n", a);//1
	printf("%d\n", a);//1
	printf("%d\n", a);//1
	printf("%d\n", num1::b);//2
    printf("%d\n", b);//error C2065: “b”: 未声明的标识符
	return 0;
}

二.输入输出

std(标准库的命名空间)//防止定义的内容与标准库冲突

C++的输入输出可以自动识别变量类型

<<是流插入运算符,>>是流提取运算符

#include<iostream>
using namespace std;
int main()
{
	cout << "1" << endl;
}

展开std标准命名空间就可以用cincout实现输入输出

三.缺省参数

1.全缺省参数

#include<iostream>
using namespace std;
void func1(int a = 10)
{
	cout << a << endl;
}
int main()
{
	
	func1();//10
}
#include<iostream>
using namespace std;
void func1(int a = 10,int b=20,int c=30)
{
	cout << a <<' '<<b<<' '<<c<< endl;
}
int main()
{
	
	func1();//10 20 30
}

2.半缺省参数

从右往左缺省(若从左往右缺省,则无法确定后面未缺省的值)

缺省参数声明和定义不能同时给,若存在声明,则只给声明缺省

缺省:不传值也能输出值

#include<iostream>
using namespace std;
void func1(int a=10 ,int b=20,int c)
{
	cout << a <<' '<<b<<' '<<c<< endl;
}
int main()
{
	
	func1(100,200);//报错:error C2548: “func1”: 缺少形参 3 的默认实参
}
#include<iostream>
using namespace std;
void func1(int a, int b = 20, int c = 30);
void func1(int a,int b,int c)
{
	cout << a <<' '<<b<<' '<<c<< endl;
}
int main()
{
	
	func1(100,200);//100 200 30
}
#include<iostream>
using namespace std;
void func1(int a, int b=20 , int c=30 );
void func1(int a,int b=40,int c=50)
{
	cout << a <<' '<<b<<' '<<c<< endl;
}
int main()
{
	
	func1(100,200);//报错:error C2572: “func1”: 重定义默认参数 : 参数 1
                   //报错:error C2572: “func1”: 重定义默认参数 : 参数 2
}
#include<iostream>
using namespace std;
void func1(int a, int b , int c );
void func1(int a,int b=40,int c=50)
{
	cout << a <<' '<<b<<' '<<c<< endl;
}
int main()
{
	
	func1(100,200);//100 200 50
}
#include<iostream>
using namespace std;
void func1(int a ,int b=20,int c=30)
{
	cout << a <<' '<<b<<' '<<c<< endl;
}
int main()
{
	
	func1(100,200);//100 200 30
}

三.函数重载

同一作用域函数名相同形参列表(参数个数 或 类型 或 类型顺序)不同

返回值不同不能说明函数重载

函数重载只有c++支持c不支持

C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载

#include<iostream>
using namespace std;
void f(int a)
{
	cout << a << endl;
}
void f(int a, int b)
{
	cout << a << ' ' << b << endl;
}
int main()
{
	f(1);//1
	f(20, 30);//20 30
                            
}
 
//参数个数不同
#include<iostream>
using namespace std;
void f(int a)
{
	cout << a << endl;
}
void f(double a)
{
	cout << a << endl;
}
int main()
{
	f(1);
	f(20.2);
}
//参数类型不同
#include<iostream>
using namespace std;
void f(double b,int a)
{
	cout << '1' << endl;
}
void f(int a,double b)
{
	cout << '2' << endl;
}
int main()
{
	f(1.1,2);//1
	f(10,20.2);//2
}
//类型顺序不同

四.引用        

取别名

类型& 引用变量名(对象名) = 引用实体;(引用类型必须和引用实体是同种类型的

1.定义时必须初始化       

#include<iostream>
using namespace std;
int main()
{
	
	int& b;//报错:error C2530: “b”: 必须初始化引用
}

2.一个变量多个引用

#include<iostream>
using namespace std;
int main()
{
	int a = 0;
	int& b = a;
	int& c = a;
	int& d = b;
}

3.引用权限放大缩小

权限放大报错,权限缩小和平移则运行成功

#include<iostream>
using namespace std;
int main()
{
	const int a = 0;
	int& b = a;//报错:error C2440: “初始化”: 无法从“const int”转换为“int &”
}
//权限放大
#include<iostream>
using namespace std;
int main()
{
	int a = 10;
	const int& b = a; 
	cout << b << endl;//10
}
//权限缩小
#include<iostream>
using namespace std;
int main()
{
	const int a = 10;
	const int& b = a; 
	cout << b << endl;//10
}
//权限平移

4.引用可影响被引用值

#include<iostream>
using namespace std;
int main()
{
	int a = 10;
	const int& b = a; 
	int& c = a;
	c++;
	cout << b << endl;//11
	cout << a << endl;//11
	cout << c << endl;//11
 
}

5.引用一旦引用一个实体,再不能引用其他实体

6.传值传参耗费时间,引用传参效率更高

7.类型转换--------产生临时变量,具有常性(由const修饰)

#include<iostream>
using namespace std;
int main()
{
	double d = 12.34;
	int i = d;
	cout << i << endl;//12
	
}
#include<iostream>
using namespace std;
int main()
{
	int x = 2, y = 3;
	const int& t = x + y;
	cout << t << endl;//5
}
#include<iostream>
using namespace std;
int main()
{
	int x = 2, y = 3;
	int& t = x + y;
	cout << t << endl;//报错:error C2440: “初始化”: 无法从“int”转换为“int &”
}
#include<iostream>
using namespace std;
int main()
{
	double d = 12.34;
	int i = d;
	int& r = d;
	cout << r << endl;//报错:error C2440: “初始化”: 无法从“double”转换为“int &”
	
}
#include<iostream>
using namespace std;
int main()
{
	double d = 12.34;
	int i = d;
	const int& r = d;
	cout << r << endl;//12
	
}

8.没有NULL的引用

#include<iostream>
using namespace std;
int main()
{
	int* ptr = NULL;
	int& r = *ptr;//这时未解引用,存的是ptr的地址,不会报错
	cout << r << endl;//此时系统崩溃
}

 9.引用为引用类型的大小,但指针为地址空间所占字节个数

#include<iostream>
using namespace std;
int main()
{
	int a = 10;
	int& b = a;
	cout << sizeof(a) << endl;//4
	cout << sizeof(int&) << endl;//4
	cout << sizeof(b) << endl;//4
}

10.有多级指针,没有多级引用

#include<iostream>
using namespace std;
int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int& b = a;
	int& b = c;//报错:error C2040: “b”:“int &”与“int”的间接寻址级别不同
}

取别名(语法上不开辟空间)

语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间

在底层实现上实际是有空间的,因为引用是按照指针方式来实现的

指针(语法上开辟空间)

int main()
{
    int a = 10;
    int& ra = a;
    ra = 20;
    int* pa = &a;
    *pa = 20;
    return 0;
}

 354d6e6d85a740b49f5f0cb88687fefc.png

引用自加即引用的实体增加 1 ,指针自加即指针向后偏移一个类型的大小

(阿里巴巴2015笔试题) 引用传值,指针地址(X)

 引用表面好像是传值,其 本质也是传地址,只是这个工作有编译器来做,所以错误

五.内联(inline)


在调用内联函数的地方展开,且没有函数调用建立栈帧,内联函数程序运行效率提高,缺陷是使目标文件变大      优势:少了调用开销,提高程序运行效率。

内联说明只是向编译器发出一个请求,编译器可以选择忽略这个请求(例如:函数规模大/递归/频繁调用)

inline 不建议声明和定义分离,分离会导致链接错误。因为 inline 被展开,就 没有函数地址
了, 链接就会找不到

be3966befd3244148f7f0b4aa115be89.png

查看方式:
1. 在release模式下,查看编译器生成的汇编代码中是否存在call Add
2. 在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不会对代码进行优化,以下给出vs2013的设置方式)

 42092e6fa61c4373ab100a6e9f5eb3df.png

43a8de852ab641a088e5c0244bce1c5b.png

宏的优缺点

宏的优缺点
优点:
1.增强代码的复用性。
2.提高性能。
缺点:
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,
3.没有类型安全的检查 。


C++有哪些技术替代宏
1. 常量定义 换用const enum
2. 短小函数定义 换用内联函数

六.auto

可以根据右边初始化判断类型

#include<iostream>
using namespace std;
int main()
{
	auto x = 10;
	auto y = 20.1;
	std::cout << typeid(x).name() << std::endl; //int
	std::cout << typeid(y).name() << std::endl; //double
 
}
 
//typeid().name 推测auto修饰变量类型

能够替代较长类型的定义,简化代码

autoauto*没有区别

#include<iostream>
using namespace std;
int main()
{
	int a = 10;
	auto b = &a;
	auto* c = &a;
	auto& d = a;
 
	cout << a << endl;//10
	cout << b << endl;//006FFA4C
	cout << c << endl;//006FFA4C
	cout << d << endl;//10
}

auto不能作为函数参数(无法推导变量类型),不能声明数组

18d98425234f48488f747665ffa1c61e.png

 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译
器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

七.c++11  for循环

自动取array数组中,赋值给e

自动++,自动判断结束

#include<iostream>
using namespace std;
int main()
{
	
		int array[] = { 1, 2, 3, 4, 5 };
		for (auto& e : array)
			e *= 2;
		for (auto e : array)
			cout << e << " ";//2 4 6 8 10
		return 0;
	
}

auto e:array      范 围内用于迭代的变量 被迭代的范围

八.nullptr

没有对应的头文件

由于c++中定义的宏  #define NULL 0

C++11中,sizeof(nullptr) sizeof((void*)0)所占的字节数相同

故:

#include<iostream>
using namespace std;
	
	void f(int)
	{
		cout << "f(int)" << endl;
	}
	void f(int*)
	{
		cout << "f(int*)" << endl;
	}
	int main()
	{
		f(0);//f(int)
		f(NULL);//f(int)
		f((int*)NULL);//f(int*)
		return 0;
	}
#include<iostream>
using namespace std;	
int main()
{
	cout << sizeof(nullptr) << endl;//4
	cout << sizeof((void*)0) << endl;//4
}

九.类

1.定义

class  name
{
    //类体:成员函数和成员变量
};

类声明在.h文件,成员函数定义在.cpp文件(成员函数名前加类名::)

2.实例化

用类类型创建对象的过程
定义出一个类没有分配实际的空间储存
一个类可以实例化多个对象, 实例化的对象占  实际的物理空间存储类成员变量

3.类对象的大小

类里既有成员变量,又有成员函数,那么一个类的对象包含

#include"iostream"
using namespace std;
class Date {
public:
	void date();
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	cout << sizeof(Date) << endl;//12
}

//既有成员函数,又有成员变量
#include"iostream"
using namespace std;
class Date {
public:
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	cout << sizeof(Date) << endl;//12
}

//仅有成员变量
#include"iostream"
using namespace std;
class Date {

};

int main()
{
	cout << sizeof(Date) << endl;//1
}

//空类,不存储数据,标识对象被定义出来
#include"iostream"
using namespace std;
class Date {

};

int main()
{
	Date a1;
	Date a2;
	cout << sizeof(Date) << endl;//1
}

 一个类的大小,实际是该类中"成员变量"之和,注意内存对齐

每个对象成员不同,但调用的成员函数相同(公共区域)

十.类访问限定符及封装

361d2a8ad6324e5ebfce8953636bbd92.png

访问限定符说明:

1.public修饰的成员可在类外直接访问,protected和private修饰的成员不能在类外被直接访问

2.访问权限作用域从访问限定符出现的位置开始,到下一个访问限定符出现为止

若后面无限定符,到  }  为止

3.class默认权限private,struct 默认权限public(structclass的区别)

访问限定符 只在编译时有用, 当数据映射到内存后,没有任何访问限定符上的区别
C++中struct和class的区别是什么?
C++中struct可以当成结构体使用,struct还可以用来定义类。和class定义类是一样的
struct定义的类默认访问权限是public,class定义的类默认访问权限是private
struct 类名;
类名就可以定义对象了

面向对象三特性:封装,继承,多态

#include <iostream>
#include <stdlib.h>
using namespace std;

class A1 {
public:
    void f1() {}
private:
    int _a;
};

int main() {
    int x = sizeof(A1);
    cout << x; // 4
    return 0;
}
#include <iostream>
#include <stdlib.h>
using namespace std;

// 类中仅有成员函数
class A2 {
public:
    void f2() {}
};

int main() {
    int x = sizeof(A2);
    cout << x; // 1
    return 0;
}
#include <iostream>
#include <stdlib.h>
using namespace std;

class A3
{};
int main() {
    int x = sizeof(A3);
    cout << x; //1
    return 0;
}

 a974f7819aba4735b0c29e56231f47e6.png

#include<iostream>
using namespace std;
// 计算⼀下A/B/C实例化的对象是多⼤?
class A
{
	public :
	void Print()
	{
		cout << _ch << endl;
	}
private:
	char _ch;
	int _i;
};
class B
{
	public :
	void Print()
	{
		//...
	}
};
class C
{};
int main()
{
	A a;
	B b;
	C c;
	cout << sizeof(a) << endl;//8
	cout << sizeof(b) << endl;//1
	cout << sizeof(c) << endl;//1
	return 0;
}

结构体内存对齐规则

1. 第一个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的对齐数为8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。


【面试题】
1. 结构体怎么对齐? 为什么要进行内存对齐?

从cpu读取数据,规定从整数倍位置开始读,减少访问次数,提高效率
2. 如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?
3. 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景

十一.this指针

this指针存在

#include"iostream"
using namespace std;
class Date {
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
    // 编译器编译后,类的成员函数默认都会在形参第⼀个位置,增加⼀个当前类类型的指针,叫做this指针。⽐如Date类的Init的真实原型为, void Init(Date* const this, int year,int month, int day)
	//void print(Date* const this)    X
	//this不能修改,但是this指向的内容可以修改
    //this++;    X
	void print()
	{
		//cout << this->_year << '-' << this->_month << '-' << this->_day;    V
		cout << _year << '-' << _month << '-' << _day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date a1(2024,7,18);
	//实参和形参的位置不能显示写,编译器自己加,但是类可以用this
	//a1.print(&a1)    X
	a1.print();//2024-7-18
}

特性:

1.指针类型:  *const,this本身不能修改        //this++;  X

2.只能在成员函数内部使用

3.本质是成员函数的形参,对象调用成员函数时,将对象地址作为实参传递给 this形参。所以对象中不存储this指针

4. this 指针是 成员函数 第一个隐含的指针形参,一般情况由编译器 通过ecx寄存器自动传递,不需要用户传递

1. this指针存在哪里?

2. this指针可以为空吗?

8f4a030ffd654537a7640d5a4aea7584.png

#include"iostream"
using namespace std;
class A
{
public:
	void Print()
	{
		cout << "Print()" << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->Print();//Print() call 地址,地址不在p里面,成员函数指针不存在对象里
                            //对象类域 ecx mov p
                            //ecx存放对象的地址 现在p是对象的地址
                            //只是传递this指针,编译时确定了地址,运行时就不会到对象里面找
	return 0;
}

//此时this指针指向nullptr的地址,并没有解引用,不会崩溃

 成员变量存放在对象里面的,解引用

#include"iostream"
using namespace std;
class A
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->PrintA();
	return 0;
}
//运行崩溃

c++实现栈 

typedef int DataType;
class Stack
{
public:
 void Init()
 {
 _array = (DataType*)malloc(sizeof(DataType) * 3);
 if (NULL == _array)
 {
 perror("malloc申请空间失败!!!");
 return;
 }
 _capacity = 3;
 _size = 0;
 }
 void Push(DataType data)
 {
 CheckCapacity();
 _array[_size] = data;
 _size++;
 }
 void Pop()
 {
 if (Empty())
 return;
 _size--;
 }
 DataType Top(){ return _array[_size - 1];}
 int Empty() { return 0 == _size;}
 int Size(){ return _size;}
 void Destroy()
 {
 if (_array)
 {
 free(_array);
 _array = NULL;
 _capacity = 0;
 _size = 0;
 }
 }
private:
 void CheckCapacity()
 {
 if (_size == _capacity)
 {
 int newcapacity = _capacity * 2;
 DataType* temp = (DataType*)realloc(_array, newcapacity *
sizeof(DataType));
 if (temp == NULL)
 {
 perror("realloc申请空间失败!!!");
 return;
 }
 _array = temp;
 _capacity = newcapacity;
 }
 }
private:
 DataType* _array;
 int _capacity;
 int _size;
};
int main()
{
 Stack s;
 s.Init();
 s.Push(1);
 s.Push(2);
 s.Push(3);
 s.Push(4);
 
 printf("%d\n", s.Top());
 printf("%d\n", s.Size());
 s.Pop();
 s.Pop();
 printf("%d\n", s.Top());
 printf("%d\n", s.Size());
 s.Destroy();
 return 0;
}

十二.类的默认成员函数 

默认成员函数:用户没有显示实现,编译器会自动生成的成员函数

1.构造函数

特点:

1.函数名和类名相同

2.不是开空间创建对象,而是初始化对象

3.无返回值

4.对象实例化时编译器自动调用对应的构造函数

5.构造函数可以重载

6.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成

#include"iostream"
using namespace std;
class Date
{
public:
	// 1.无参构造函数
	Date()
	{}

	
	void print()
	{
		cout << _year << '/' << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1; // 调用无参构造函数
	d1.print();
	
}

0596b192a6e344eba45e3cfb06bbd4f2.png

 无参的和带参的同时存在时存在调用歧义

#include"iostream"
using namespace std;
class Date
{
public:
	// 1.无参构造函数
	Date()
	{}

	// 2.带参构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	/*void print()
	{
		cout << _year << '/' << _month << "/" << _day << endl;
	}*/
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1; // 调用无参构造函数
	Date d2(2015, 1, 1); // 调用带参的构造函数
	Date d3();//无参构造函数创建对象,对象后面不跟括号,否则变成函数声明
	// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
	 // warning C4930: “Date d3(void)”: 未调用原型函数
	//d1.print();
	
}

 显示定义了构造函数,编译器无法自动生成,但是由于不传参,没有合适的默认的构造函数

#include"iostream"
using namespace std;
class Date
{
public:
	
	// 如果用户显式定义了构造函数,编译器将不再生成
	Date(int year, int month, int day)
	{
	_year = year;
	_month = month;
	_day = day;
	}
	

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	// 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
	// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成
	// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
	Date d1;
	return 0;
}

对于默认构造函数

自定义类型                          class/struct/union

内置类型/基本类型              int/double/char/指针

编译器自动生成默认构造函数,对于内置类型,没有规定要不要做处理

                                                  对于自定义类型,调用它的无参构造(不传参就能调用的构造)

#include"iostream"
using namespace std;
class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year;
	int _month;
	int _day;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d;
	return 0;
}

1bcb859866ed4d4080b5b3ee4302e838.png

自动生成的构造函数   --->栈实现队列MyQueue

内置类型可以在声明时给默认值

默认构造函数(三者只能存在一个)

1.无参构造函数

2.全缺省构造函数

3.没写编译器默认生成的构造函数

一般构造函数都需要自行实现,少部分可以让编译器自动生成,例如:MyQueue,成员都是自定义类型,调用stack的默认构造(全缺省构造)

如果自定义类型没有默认构造呢?初始化列表

#include"iostream"
using namespace std;
class Date
{
public:
	Date()
	{
		_year = 1900;
		_month = 1;
		_day = 1;
	}
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;//error C2668: “Date::Date”: 对重载函数的调用不明确
	
}

2.析构函数

对象在销毁时会自动调用析构函数, 完成对象中资源的清理工作
创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数
#include<iostream>
using namespace std;
typedef int STDataType;
class Stack
{
	public :
	Stack(int n = 4)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		} _
			capacity = n;
		_top = 0;
	} ~
		Stack()
	{
		cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	STDataType* _a;
	size_t _capacity;
	size_t _top;
};
// 两个Stack实现队列
class MyQueue
{
public :
	//编译器默认⽣成MyQueue的析构函数调⽤了Stack的析构,释放的Stack内部的资源
	// 显⽰写析构,也会⾃动调⽤Stack的析构
/*~MyQueue()
{}*/
private:
	Stack pushst;
	Stack popst;
};
int main()
{
	Stack st;
	MyQueue mq;
	return 0;
}

1.内置类型不做处理,自定义类型调用析构

2.类名前加~

3.无参无返回值类型

4.一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载

5.对象生命周期结束时,C++编译系统系统自动调用析构函数

6.有资源需要清理写析构,如:Stack,List

   不需要写析构:

                        a.没有资源需要清理:如:Date

                        b.内置类型成员没有资源需要清理,剩下都是自定义类型成员,如MyQueue

7.⼀个局部域的多个对象,C++规定后定义的先析构

8.显⽰写析构函数,对于⾃定义类型成员也会调⽤他的析构,也就是说⾃定义类型成员⽆论什么情况都会⾃动调⽤析构函数

自定义的析构不需要显示写

自动生成的析构只管内置类型

3.拷贝构造

Stack s1(s2);

Stack s3=s1;

用同类型的对象初始化

形参是对 该类类型对象的引用(一般用const修饰, 预防被拷贝的数据被修改)

1.构造函数的一个重载形式
2. 第一个参数是自身类类型对象的引用,使用 传值方式编译直接报错,因为会 引发无穷递归
(传值传参调用拷贝构造)
633997eabc9f4b948cb7ca10d35d11a5.png
// 编译报错:error C2652 : “Date”: ⾮法的复制构造函数: 第⼀个参数不应是“Date”
//Date(Date d)
Date(const Date & d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
0bc526daa0b44b5b9a33ba3dc6e2bd17.png
本来要调用拷贝构造,但是要先传参,然后传值传参要调用拷贝构造, 然后传参数(传值传参)调用新的拷贝构造
改进:
719b6f56b3664eb3af99afc61bf155e9.png
3.未显示定义会生成默认的拷贝构造, 内置类型完成值拷贝(浅拷贝),即按字节拷贝
栈需要深拷贝,写拷贝函数,否则指向同一个地址析构两次,系统崩溃

4.未显示定义拷贝构造时,编译器默认生成拷贝构造函数

       对内置类型---值拷贝/浅拷贝----一个字节一个字节拷贝,拷贝指针以及指向的资源

       对自定义类型---拷贝构造

5.传引用比传值少了"拷贝",但一定要确定无局部变量干扰,否则成野指针

可以给局部变量加"static"

传值返回会产生一个临时对象调用拷贝构造,传引用返回的是对象的别名,没有产生拷贝,但如果返回对象是当前函数局部域的局部对象,函数结束就销毁了,即使用引用返回是有问题的(即:此时的引用相当于一个野引用,野指针)

Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器⾃动⽣成的拷⻉构造完成的值拷⻉/浅拷⻉不符合我们的需求,所以需要⾃⼰实现深拷⻉(对指向的资源也进⾏拷⻉)

f5d291fc4674463c8f5a8e8e7cee6e05.png

析构同一块空间两次,程序崩溃

6aedb5cd33714e0c81984793a19e24ab.png

6.类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请

时,则 拷贝构造函数一定要写的,否则就是浅拷贝

7.拷贝构造函数典型调用场景:

        a.使用已存在对象创建新对象

        b.函数参数类型为类类型对象

        c.函数返回值类型为类类型对象

传值返回会产⽣⼀个临时对象调⽤拷⻉构造,传值引⽤返回,返回的是返回对象的别名(引⽤),没有产⽣拷⻉。但是如果返回对象是⼀个当前函数局部域的局部对象,函数结束就销毁了,那么使⽤引⽤返回是有问题的,这时的引⽤相当于⼀个野引⽤,类似⼀个野指针⼀样。传引⽤返回可以减少拷⻉,但是⼀定要确保返回对象,在当前函数结束后还在,才能⽤引⽤返回
3a6555cad0ed4d10ba2cfb964bf19d1b.png

                                                                           error:返回局部对象的地址

a84c6710de3e40db84dd803667d17293.png

#include"iostream"
using namespace std;
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// Date(const Date& d)   // 正确写法
	Date(const Date d) 
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void print()
	{
		cout << _year << '-' << _month << '-' << _day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2(d1);
	d2.print();//error C2652: “Date”: 非法的复制构造函数: 第一个参数不应是“Date”
	return 0;
}

4.再探构造函数(初始化列表)

声明是给缺省值用于初始化列表

655ab81ca1024045b01e0549196f976f.png

1.初始化列表写了,就不用缺省值了

2.初始化列表没有写,用缺省值

3.都没有内置类型就取决于编译器

4.自定义类型调用自己的默认构造,没有就报错

初始化只能初始化一次,而构造函数体 内可以多次赋值

660257707b844a42b2ca94bd805f84d7.png

初始化列表( 每个成员变量定义初始化的地方):冒号开始,逗号分割,每个“成员变量”后面跟一个放在括号内的初始值或表达式
注意:
定义的时候必须初始化
139cecbad87b4651b75e251a312c18f1.png 7890325f8f734c4398f8799138f880c9.png
1. 每个成员变量在初始化列表中 只能出现一次(初始化只能初始化一次)
2. 类中包含以下成员, 必须放在初始化列表位置进行初始化:
         引用成员变量
        const成员变量
        自定义类型成员(且该类没有默认构造函数时)
440967e6b9dc4f7fad0d7743ccd153bb.png
3. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化
4. 成员变量在类中 声明次序就是其在初始化列表中的 初始化顺序,与其在初始化列表中的先后次序无关
5.尽量使⽤初始化列表初始化,因为那些你不在初始化列表初始化的成员也会⾛初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会⽤这个缺省值初始化。如果你没有给缺省值,对于没有显⽰在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有显⽰在初始化列表初始化的⾃定义类型成员会调⽤这个成员类型的默认构造函数,如果没有默认构造会编译错误
 
#include"iostream"
using namespace std;
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()
}
#include"iostream"
using namespace std;
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();
}

b2a8127fb3b14f228674e0bef940eec4.png

#include<iostream>
using namespace std;
class Time
{
public:
	Time(int hour)
		:_hour(hour)
	{
		cout << "Time()" << endl;
	}
private:
	int _hour;
};

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)//这里的缺省值是看是否传参数,但所有变量都会走初始化列表,
												  //所以最后的只有初始化列表和声明缺省值决定
		:_year(year)
		,_month(month)
	{}

	void Print() const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	// C++11
	// 声明,缺省值->初始化列表用的
	int _year = 1;
	int _month = 1;
	int _day;

	int* _ptr = (int*)malloc(12);
	Time _t = 1;
};

int main()
{
	// 对象定义
	Date d1(2024, 7, 14);
	d1.Print();

	//Date d2;
	//d2.Print();


	return 0;
}

 be5746e951ec430a95ea2c6ba4c63e98.png

dcf5383ece42412196c366ebc14dcdc7.png

十三.赋值运算符重载

1.运算符重载

operator 后面接需要重载的运算符符号
函数原型返回值类型  operator 操作符 ( 参数列表 )
重载运算符函数的参数个数和该运算符作⽤的运算对象数量⼀样多
 
如果⼀个重载运算符函数是成员函数,则它的 第⼀个运算对象默认传给隐式的this指针,因此运算符重载作为 成员函数时,参数⽐运算对象少⼀个
运算符重载以后,其优先级和结合性与对应的内置类型运算符保持⼀致
重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第⼀个形参位置,第⼀个形参位置是左侧运算对象,调⽤时就变成了对象<<cout,不符合使⽤习惯和可读性。重载为全局函数把ostream/istream放到第⼀个形参位置就可以了,第⼆个形参位置当类类型对象。
c24dd4f767b34feda7e78f262fa456cd.png
C++规定类类型对象使⽤运算符时,必须转换成调⽤对应运算符重载,若没有对应的运算符重载,则会编译报错
重载操作符⾄少有⼀个类类型参数,不能通过运算符重载改变内置类型对象的含义,如: int
operator+(int x, int y)
1.作为 类成员函数重载时,其 形参看起来 比操作数数目少 1,因为成员函数的 第一个参数为隐
藏的this

2.         .*        ::       sizeof       ?:       .       注意以上5个运算符不能重载

c++规定成员函数要加&才能取到函数指针

093e248ec6e944b3a04af454940a37c2.png

c2053cd2235c43aca143c20b52c07a71.png函数回调

#include"iostream"
using namespace std;
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		 _year = year;
		 _month = month;
		 _day = day;
	}
        //成员函数       
		// bool operator==(Date* this, const Date& d2)
		// 这里需要注意的是,左操作数是this,指向调用函数的对象
		bool operator==(const Date& d2)
	{
		return _year == d2._year
			&& _month == d2._month
			&& _day == d2._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2024, 7, 18);
	Date d2(2021, 3, 1);
	cout << d1.operator==(d2) << endl;//0
}
#include"iostream"
using namespace std;
// 全局的operator==
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
// 这里会发现运算符重载成全局的就需要成员变量是公有的
// 友元解决或者重载成成员函数。
bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}
int main()
{
	Date d1(2024, 7, 18);
	Date d2(2021, 3, 1);
	cout << operator==(d1,d2) << endl;
//error C2248: “Date::_year”: 无法访问 private 成员(在“Date”类中声明
}
#include"iostream"
using namespace std;
// 全局的operator==
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
//private:
	int _year;
	int _month;
	int _day;
};
// 这里会发现运算符重载成全局的就需要成员变量是公有的
// 友元解决或者重载成成员函数。
bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}
int main()
{
	Date d1(2024, 7, 18);
	Date d2(2021, 3, 1);
	Date d3(2024, 7, 18);
	cout << operator==(d1,d2) << endl;//0
	cout << (d1 == d3) << endl;//1
}

//全局的operator,将private变量变为公有

2.赋值运算符重载

⽤于完成两个已经存在的对象直接的拷⻉赋值

196d593780da4a3fabd87b61fcd5a6c8.png

a.参数类型const T&,传递引用可以提高传参效率(赋值运算重载的参数建议写成const当前类类型引⽤,否则会传值传参会有拷⻉)

b.默认赋值重载浅拷贝

c.返回值类型T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值

 有返回值,且建议写成当前类类型引⽤,引⽤返回可以提⾼效率,有返回值⽬的是为了⽀持连续赋值的场景

410bb24c6a42499badcd016405061fd5.png

90c18090887d4e749334ae96e7d3f9b2.png

d.返回*this :要符合连续赋值的含义

e.赋值运算符只能重载成类的成员函数不能重载成全局函数

#include"iostream"
using namespace std;

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}

		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2024, 7, 18);
	Date d2(2021, 3, 1);
	Date d3(2024, 7, 18);
	d1.operator=(d3);
}

赋值运算符operator为全局时,注意全局函数没有this指针,需要给两个参数

#include"iostream"
using namespace std;
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
//private:
	int _year;
	int _month;
	int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{
	if (&left != &right)
	{
		left._year = right._year;
		left._month = right._month;
		left._day = right._day;
	}
	return left;
}
int main()
{
	Date d1(2024, 7, 18);
	Date d2(2021, 3, 1);
	Date d3(2024, 7, 18);
	
}

// error C2801: “operator =”必须是非静态成员
原因:赋值运算符如果 不显式实现,编译器 会生成一个默认的。此时用户 再在类外自己实现
一个全局的赋值运算符重载,就 和编译器在类中生成的默认赋值运算符重载冲突了,故赋值
运算符重载 只能是类的成员函数

f.用户没有显式实现时,编译器会生成一个默认赋值 运算符重载,以值的方式逐字节拷贝

注意:

        内置类型成员变量是直接赋值的

        自定义类型成员变量需要调用对应类的赋值运算符 重载完成赋值。

3.前置++和后置++重载

#include"iostream"
using namespace std;
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// 前置++:返回+1之后的结果
	// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
	Date& operator++()
	{
		_day += 1;
		return *this;
	}
	void print()
	{
		cout << _year << '-' << _month << '-' << _day << endl;
	}
	// 后置++:
	// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
	// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器
	// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
	//一份,然后给this + 1
	//而temp是临时对象,因此只能以值的方式返回,不能返回引用
		Date operator++(int)
		{
		Date temp=(*this);
		_day += 1;
		return temp;
		}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d;
	Date d1(2022, 1, 13);
	d = d1++;
	d.print();
	d1.print();

	Date d3(2024, 7, 18);
	Date d4 = ++d3;
	d3.print();
	d3.print();
	return 0;
}

e25cf7133d9349b9ab8e338edfa9b137.png

日期类代码实现

min.cpp

#include"min.h"
Date& Date::operator+=(int 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) 
{
	Date tmp = *this;
	tmp += day;
	return tmp;
}
void Date::print()
{
	cout << _year << '-' << _month << '-' << _day << endl;
}

Date Date::operator-(int day)
{
	Date tmp = *this;
	tmp._day -= day;
	while (tmp._day <= 0)
	{
		
		tmp._month--;
		if (tmp._month == 0)
		{
			tmp._year--;
			tmp._month = 12;
		}
		tmp._day += GetMonthDay(tmp._year, tmp._month);
	}
	return tmp;
}

bool Date::operator==(const Date& d) const
{
	return this->_year == d._year &&
		this->_month == d._month &&
		this->_day == d._day;
}

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 !(*this < d) && (!(*this == d));
}
//bool Date::operator>=(const Date& d) const
//{
//	return !(operator<(d));
//}

bool Date::operator!=(const Date& d) const
{
	return !(operator==(d));
}

int Date::operator-(const Date& d) const
{
	int flag = 1;
	Date max = *this;
	Date min = d;
	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}

	int n = 0;
	while (min != max)
	{
		++min;
		++n;
	}

	return n * flag;

}

Date& Date::operator++()
{
	*this += 1;
	return *this;
}


bool Date::operator>=(const Date& d) const
{
	return *this > d || *this == d;
}

Date Date::operator++(int day)
{
	Date tmp = *this;
	*this += 1;
	return tmp;
}

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}

bool Date::CheckDate() const
{
	if (_month < 1 || _month > 12
		|| _day < 1 || _day > GetMonthDay(_year, _month))
	{
		return false;
	}
	else
	{
		return true;
	}
}


istream& operator>>(istream& in, Date& d)
{
	while (1)
	{
		cout << "请依次输入年月日:>";
		in >> d._year >> d._month >> d._day;

		if (!d.CheckDate())
		{
			cout << "输入日期非法:";
			d.print();
			cout << "请重新输入!!!" << endl;
		}
		else
		{
			break;
		}
	}

	return in;
}

 min.h

#pragma once
#include"iostream"
#include"cstdlib"
using namespace std;
class Date
{
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	int GetMonthDay(int year, int month) const
	{
		static int day[13] = { 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 day[month];
	}

	bool CheckDate() const;
	void print();
	Date operator+(int day);
	Date& operator+=(int day);

	Date operator-(int day);
	Date& operator-=(int day);

	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;

	int operator-(const Date& d) const;

	Date& operator++();
	Date operator++(int);

	Date* operator&()
	{
		return this;
	}

	const Date* operator&() const
	{
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);

test.cpp

#include"min.h"
int main()
{
	int day = 10;
	Date d1(2024, 7, 29);
	/*d1.print();
	Date d2 = (d1 += day);
	d1.print();
	d2.print();*/

	Date d2=d1+100;
	d1.print();
	d2.print();

	Date d3 = d1 - 100;
	d3.print();


	const Date d5 = d1;

	Date d4 = d1;
	cout << d4.Date::operator==(d1) << endl;
	cout << (d1.operator<(d2)) << endl;
	cout << (d1.operator>=(d2)) << endl;
	cout << (d1.operator!=(d2)) << endl;
	cout << abs(d1.operator-(d2)) << endl;
	cout << d1 - d2 << endl;
	cout << (d1 >= d2) << endl;
	cout << (d1 >= d3) << endl;
	cout << &d1 << endl;
	cout << &d5 << endl;
	d1++;
	d1.print();
}

KY111 日期差值 

HJ73 计算日期到天数转换

JZ64 求1+2+3+...+n

KY258 日期累加

KY222 打印日期

十四.const 成员

不修改成员变量的函数可以加const

const修饰类成员函数, 实际修饰该成员函数 隐含的this指针,表明在该成员函数中 不能对类的任何成员进行修改,即Date* const this

eff6ed023d624e72b4c5e3efd807bb29.png

#include"iostream"
using namespace std;
class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << "Print()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
	void Print() const
	{
		cout << "Print()const" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
int main()
{
	Date d1(2024, 7, 13);
	d1.Print();

	const Date d2(2022, 1, 13);
	d2.Print();
}

815090e473f849b5875ff5af889a1707.png

涉及到权限,权限可以缩小和平移 --------- 指向的内容

#include"iostream"
using namespace std;
class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << "Print()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
	/*void Print() const
	{
		cout << "Print()const" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}*/
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
int main()
{
	

	const Date d2(2022, 1, 13);
	d2.Print();//error C2662: “void Date::Print(void)”: 不能将“this”指针从“const Date”转换为“Date &”
}

取地址及const取地址操作符重载

普通取地址运算符重载     和      const取地址运算符重载

默认成员函数不需要重载,没有的话编译器会自动生成,除非一些特殊情况,否则可以不需要显示实现,且取地址取适配的取地址重载,若没有普通的取地址运算符重载适配,则会去去编译器默认生成的

#include"iostream"
using namespace std;
class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void print()
	{
		cout << _year << '-' << _month << '-' << _day << endl;
	}
	Date* operator&()
	{
		return this;
	}
	const Date* operator&()const
	{
		return this;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
int main()
{
	Date d1(2024, 7, 18);
	cout << &d1 << endl;

	const Date d2(2020, 3, 4);
	cout << &d2 << endl;
}

a6216bb9c4a042088ef4de6c82fe5ebc.png

十五.static(类的静态成员)

静态成员变量一定要在类外初始化

1. 静态成员所有类对象所共享,不属于某个具体的对象,存放在静态区
2. 静态成员变量必须在 类外定义,定义时不添加static关键字,类中只是声明
3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问(突破类域)
ed57ddf673d84a9e9f0286596d89d46d.png
4. 静态成员函数 没有隐藏的 this指针,不能访问任何非静态成员
cc7dd922598e426a817398312e5338c5.png
非静态成员函数可以访问任意的静态成员变量和静态成员函数
5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
类里声明的静态变量,静态的全局,只受类域限制,生命周期全局,私有情况下只能在类利使用

实现一个类,计算程序中创建出了多少个类对象

01:37:50

#include"iostream"
using namespace std;
class A
{
public:
	A() { ++_scount; }
	A(const A& t) { ++_scount; }
	~A() { --_scount; }
	static int GetACount() { return _scount; }
private:
	static int _scount;
};
int A::_scount = 0;
int main()
{
	cout << A::GetACount() << endl;
	A a1, a2;
	A a3(a1);
	cout << A::GetACount() << endl;
}

2b87eacd8b9543edba6354b2d823db7e.png

#include"iostream"
using namespace std;
class A
{
public:
	A() { ++_scount; }
	A(const A& t) { ++_scount; }
	~A() { --_scount; }
	static int GetACount() { return _scount; }
private:
	static int _scount;
};
int A::_scount = 0;
int main()
{
	cout << A::GetACount() << endl;
	A a1, a2;
	{
		A a3(a1);
	}
	cout << A::GetACount() << endl;
}

6f21c48cd3b24469b6479d59a3b8719a.png

JZ64 求1+2+3+...+n

class Sum
{
	public :
	Sum()
	{
		_ret += _i;
		++_i;
	}
	static int GetRet()
	{
		return _ret;
	}
private:
	static int _i;
	static int _ret;
};
int Sum::_i = 1;
int Sum::_ret = 0;
class Solution {
public:
	int Sum_Solution(int n) {
		// 变⻓数组
		Sum arr[n];
		return Sum::GetRet();
	}
};

A:D B A C
B:B A D C
C:C D B A
D:A B D C
E:C A B D
F:C D A B
C c;
int main()
{
A a;
B b;
static D d;
return 0;
}

构造顺序        E

 全局变量在main函数之前初始化

局部静态成员在运行到此的第一步初始化

析构顺序        B

 后定义的先析构

局部变量先析构

局部静态成员生命周期全局

局部析构,再析构全局

 十六.类型转换

• C++⽀持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数、
构造函数前⾯加explicit就不再⽀持隐式类型转换

5b5da34161554e6fa2a1659041a0c452.png

单参数构造函数支持,多个参数默认不支持直接写   A aa2=1,1;

cd8caba82d584484a9a094957bc10a48.png

#include<iostream>
using namespace std;
class A
{
	public :
	// 构造函数explicit就不再⽀持隐式类型转换
	// explicit A(int a1)
	A(int a1)
		: _a1(a1)
	{}
	//explicit A(int a1, int a2)
	A(int a1, int a2)
		:_a1(a1)
		, _a2(a2)
	{}
	void Print()
	{
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a1 = 1;
	int _a2 = 2;
};
int main()
{
	// 1构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造aa3
	// 编译器遇到连续构造+拷⻉构造->优化为直接构造
	A aa1 = 1;
	aa1.Print();

    //构造函数
	A aa3(1);
	aa3.Print();

	//隐式类型转换
	//先用2构造一个临时对象,再用临时对象去拷贝构造aa2
	A aa2 = 2;
	aa2.Print();


	A& raa1 = aa2;
	//A & aa2=1;  不对,因为隐式类型转换产生临时变量,具有常性,所以要加const
	const A& aa2 = 1;

	// C++11之后才⽀持多参数转化
	A aa3 = { 2,2 };
    const A& raa6={2,2};

	return 0;
}

十七.友元(友元函数和友元类)

⼀种突破类访问限定符封装的⽅式
函数声明或者类声明前⾯加friend,并且把友元声明放到⼀个类的⾥⾯

#include<iostream>
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;
};
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;
}


⼀个函数可以是多个类的友元函数
友元会增加耦合度,破坏了封装,所以友元不宜多⽤
友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员

#include<iostream>
using namespace std;
class A
{
	// 友元声明/
	friend class B;
private:
	int _a1 = 1;
	int _a2 = 2;
};
class B
{
	public :
	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.func1(aa);
	return 0;
}


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

友元类的关系是单向的,不具有交换性,⽐如A类是B类的友元,但是B类不是A类的友元
友元类关系不能传递,如果A是B的友元,B是C的友元,但是A不是c的友元


十八.内部类

如果⼀个类定义在另⼀个类的内部,这个内部类就叫做内部类
内部类默认是外部类的友元类
内部类是⼀个独⽴的类,跟定义在全局相⽐,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类
 

#include<iostream>
using namespace std;
class A
{
	private :
	static int _k;
	int _h = 1;
public:
	class B // B默认就是A的友元
	{
		public :
			void foo(const A& a)
			{
				cout << _k << endl; //OK
				cout << a._h << endl; //OK
			}
	};
};
int A::_k = 1;
int main()
{
	cout << sizeof(A) << endl;
	A::B b;
	A aa;
	b.foo(aa);
	return 0;
}
class Solution {
	// 内部类
	class Sum
	{
		public :
		Sum()
		{
			_ret += _i;
			++_i;
		}
	};
	static int _i;
	static int _ret;
public:
	int Sum_Solution(int n) {
		// 变⻓数组
		Sum arr[n];
		return _ret;
	}
};
int Solution::_i = 1;
int Solution::_ret = 0;

十九.匿名对象

类型(实参)定义出来的对象叫做匿名对象

A aa1;//有名对象

A();
A(1);//匿名对象

相⽐之前我们定义的类型对象名(实参)定义出来的叫有名对象
匿名对象⽣命周期只在当前⼀⾏,⼀般临时定义⼀个对象当前⽤⼀下即可,就可以定义匿名对象

    A aa1;  //有名对象

	// 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
	//A aa2();

	// 生命周期只在当前一行
	A(); // 匿名对象
	A(1);

	Solution st;
	cout << st.Sum_Solution(10) << endl;

	// 为了更方便
	cout << Solution().Sum_Solution(10) << endl;
    

/*greater<int> gt;
	sort(a, a + 8, gt);*/ //有名对象

	sort(a, a + 8, greater<int>());//匿名对象

二十.对象拷⻉时的编译器优化

现代编译器会为了尽可能提⾼程序的效率,在不影响正确性的情况下会尽可能减少⼀些传参传返回值过程中可以省略的拷⻉

#include<iostream>
using namespace std;

class A
{
public:
	A(int a1 = 0, int a2 = 0)
		:_a1(a1)
		, _a2(a2)
	{
		cout << "A(int a1 = 0, int a2 = 0)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a1 = aa._a1;
		}
		return *this;
	}

	~A()
	{
		//delete _ptr;
		cout << "~A()" << endl;
	}

	void Print()
	{
		cout << "A::Print->" << _a1 << endl;
	}

	A& operator++()
	{
		_a1 += 100;

		return *this;
	}
private:
	int _a1 = 1;
	int _a2 = 1;
};

int main()
{
	A aa1 = 1;//构造一个临时对象,然后拷贝构造 ---优化成----> 直接构造
	const A& aa2 = 1;//构造一个临时对象,然后引用了这个临时对象   ---就是--->  直接构造

	return 0;
}

 传参返回

#include<iostream>
using namespace std;

class A
{
public:
	A(int a1 = 0, int a2 = 0)
		:_a1(a1)
		, _a2(a2)
	{
		cout << "A(int a1 = 0, int a2 = 0)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a1 = aa._a1;
		}
		return *this;
	}

	~A()
	{
		//delete _ptr;
		cout << "~A()" << endl;
	}

	void Print()
	{
		cout << "A::Print->" << _a1 << endl;
	}

	A& operator++()
	{
		_a1 += 100;

		return *this;
	}
private:
	int _a1 = 1;
	int _a2 = 1;
};


void f1(A aa)
{}

int main()
{
	A aa1(1);
	f1(aa1);
	cout << endl;

	// 优化
	f1(A(1));//匿名对象,构造再拷贝构造优化成构造
	cout << endl;

	// 优化
	f1(1);//隐式类型转换,构造+拷贝构造优化为构造
	cout << endl;
 
	return 0;
}

 传返回值--------出函数临时变量销毁,构造的临时对象拷贝构造

#include<iostream>
using namespace std;

class A
{
public:
	A(int a1 = 0, int a2 = 0)
		:_a1(a1)
		, _a2(a2)
	{
		cout << "A(int a1 = 0, int a2 = 0)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a1 = aa._a1;
		}
		return *this;
	}

	~A()
	{
		//delete _ptr;
		cout << "~A()" << endl;
	}

	void Print()
	{
		cout << "A::Print->" << _a1 << endl;
	}

	A& operator++()
	{
		_a1 += 100;

		return *this;
	}
private:
	int _a1 = 1;
	int _a2 = 1;
};


void f1(A aa)
{}

A f2()
{
	A aa(1);
	++aa;

	return aa;
}

int main()
{
	f2().Print();
	cout <<"*********"<< endl << endl;

	return 0;
}

vs2019 先析构aa,再析构临时对象

vs2022 直接构造出临时对象来打印,省略了拷贝构造aa,析构的是临时对象

省掉了临时对象,第一次析构的是aa,第二次析构ret

#include<iostream>
using namespace std;

class A
{
public:
	A(int a1 = 0, int a2 = 0)
		:_a1(a1)
		, _a2(a2)
	{
		cout << "A(int a1 = 0, int a2 = 0)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a1 = aa._a1;
		}
		return *this;
	}

	~A()
	{
		//delete _ptr;
		cout << "~A()" << endl;
	}

	void Print()
	{
		cout << "A::Print->" << _a1 << endl;
	}

	A& operator++()
	{
		_a1 += 100;

		return *this;
	}
private:
	int _a1 = 1;
	int _a2 = 1;
};


void f1(A aa)
{}

A f2()
{
	A aa(1);
	++aa;

	return aa;
}

int main()
{
	A ret = f2();
	ret.Print();
	cout << "*********" << endl << endl;

	return 0;
}

VS2022 

vs2019

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值