文章目录
- 引用
 - auto
 - NULL&nullptr&0
 
- 类和对象
 - 类的实例化
 
- 默认成员函数
 - 构造函数
 - 析构函数
 - 拷贝构造函数
 
- 运算符的重载
 - 赋值运算符的重载
 - 拷贝构造次数编译器优化
 
- 前置++后置++
 - > < != == += + - -=
 - const成员
 - operator>>&&operator<<
 
- 再谈构造函数
 - 初始化列表初始化
 - explicit 禁止隐士类型转换
 - static成员
 - C++初始化成员新玩法-缺省值
 - 友元
 - 内部类
 - this注意
 - 友元
 - 内部类
 - this注意
 
“>>”:流提取运算符
“<<”:流插入运算符
引用
取别名的过程中,权限可以缩小但是不能够放大。
int main()
{
	int a = 0;
	int& b = a;
	int* p = &a;
	//应用取别名的过程中,权限可以缩小但是不能放大
	//加上const是权限的缩小
	const int c = 10;
	const int& d = c;
	const int& f = a;
	
	double dd = 1.1;
	//类型转换,中间都会产生了临时变量
	//dd->临时变量->ii
	int ii = dd;
	//rii引用的是临时变量,而临时变量具有常性,所以采用const int &
	const int& rii = dd;
	//函数传参和返回的时候都会产生临时变量
	return 0;
}
 
在语法层面上,引用是不开空间,仅仅是对变量取别名.指针是开空间,存储变量地址.
底层汇编实现时,引用就是用指针实现的.传值返回,类型转换会产生临时变量,再赋值给别人。
auto
自动推导变量类型.不能作为函数的形参类型,不能直接声明数组.
	int x = 10;
	auto a = &x;
	auto* b = &x;//int*
	auto& c = x;
	cout << typeid(a).name() <<"->" << typeid(b).name() << "->" << typeid(c).name() << endl;
	const int x1 = 10;
	auto y = x1;
	const auto z = x1;
	cout << typeid(y).name() << endl;//int
	cout << typeid(z).name() << endl;//int
 
//语法糖,范围for 必须是数组名
for(auto e:array)//依次将数组中的数字赋值给e
{
	cout<<e<<endl;
}
//想修改数组元素值
for(auto& e:array)
{
	cout<<e<<endl;
}
 
数组传参的过程中,数组名会退化为相应的指针类型,但是范围for中必须是数组名.
void TestForFor(int a[])
{
	for (auto e : a)//a为指针类型会报错
	{
		cout << e << endl;
	}
}
int main()
{
	int a[] = {1,2,3,4,5};
	TestForFor(a);
}
 
NULL&nullptr&0
极端场景下回存在调用的歧义,所以C++推荐使用nullptr类型是int*,C++11作为关键字引入的.sizeof(nullptr)与sizeof(void*)占用的字节数相同.

类和对象
兼容C里面结构体struct的用法,也升级为了类.
struct A
{
	int year;
	int day;
	int month;
	char name[10];
};
int main()
{
	struct A a;//C结构体用法
	A a1;		//升级为类的用法
	strcpy(a1.name,"李四");
	strcpy(a.name,"张三");
	cout << a.name << endl;
	cout << a1.name << endl;
}
 
struct 默认是公有的,class默认是私有的。
C语言,方法和结构体变量是分离的.
- 面向对象编程的三大特性:封装,继承,多态.
 
封装:数据和方法放在一起&&访问限定符实现的封装.
class Stack
{
	int _top;
	int* _a;
	int _cap;
public:
	void InitStack()
	{
		_top = _cap = 0;
		_a = nullptr;
	}
	void Push()
	{}
	void Pop()
	{}
	int Top()
	{
		assert(_top);
		return _a[_top];
	}
};
class B
{
    public:
    void F()
    {}
};
int main()
{
	B b;
	b.InitB("王五");
    cout<<sizeof(Stack)<<endl;//12
    cout<<sizeof(B)<<endl;//空类会给一字节,不存储有效数据,只是占位表示类存在
}
 
- 为什么是
12? 
对象的里面只考虑成员变量,不考虑成员函数。成员函数放到一个公共代码区.
类的实例化
对象的里面只考虑成员变量,不考虑成员函数.数据存放在类实例化的对象里面,图纸不能存,房子里面才能住人啊.函数不在对象里面存储.
不同的对象调用的是类的同一个成员函数,不同的对象实例化的时候设置不同的成员变量。
但是在函数中如何会找到不同对象的成员变量?隐藏的this 指针。
class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
	//void Init(Date* this,int y, int m, int d)
	void Init(int y, int m, int d)
	{
		_year = y;
		_month = m;
		_day = d;
	}
	//void Print(Date* this)
	void Print()
	{
		cout << _year << "-" << _month << "-" <<_day<< endl;
        cout << this->_year << "-" << this->_month << "-" <<this->_day<< endl;
	}
};
int main()
{
	Date d;
	d.Init(2023, 1, 7);
	d.Print();
	Date d1;
	d1.Init(2023, 1, 4);
	d1.Print();
	return 0;
}
 
-  
函数调用的时候隐藏传对象的地址,形参隐藏接收this指针。但是需要注意:
1.调用成员函数时,不能显示的传实参。
2.定义成员函数时,不能显示的写形参。void Print(Date* this)显示写是错的
3.在成员函数内部,可以写this。类里面有些场景是需要this的。this->_year
4.在调用的成员函数中,this指针是不可修改的,因为对象的调用过程中已经确认了,是右值。指针不可修改,但是指针指向的成员变量是可以修改的。
5.this指针是一个形参,存在于栈空间中。有些编译器会放到寄存器当中,例如vs2013,ecx当中。
 
const在指针类型之前,是指针指向的内容是不可更改的,限制只读。
 const在指针类型之后,是指针不可更改,指向的对象的自身内容是可以更改的。

-  
违反语法是编译报错。检查出空指针是运行时错误。
空指针调用成员函数不会编译报错,因为这不是语法错误,编译器检查不出来。
也不会出现空指针访问,因为调用成员函数没有存在对象里面,在一个公共代码区.会把实参传给隐藏的this指针,在没有进行空指针的解引用中,不会出错。传过来个空指针没问题,只要不解引用成员变量就行。
class A { public: void Show() { cout << "show" << endl; } }; class B { int _a; public: void PrintA() { cout << _a << endl;//崩溃在this->_a是nullptr->_a空指针了 } }; int main() { A* p = nullptr; p->Show();//正常运行完毕 B* p1 = nullptr; p1->PrintA();//崩溃 } 
默认成员函数
C语言写完之后,忘记初始化造成成员崩溃,忘记清理资源导致可用资源很少.所以提供了默认成员函数.
特点:如果我们不实现,系统会自己生成。
构造函数
他是特殊的成员函数,对象定义出来流自动调用,确保创建的对象都被初始化了.
 初始化对象,并不是创建对象开空间。
  函数名和类名相同,没有返回值
  对象实例化的过程中自动调用,构造函数可以重载。
  推荐实现全缺省或者半缺省。语法上无参的和全缺省的函数可以同时存在,但是如果存在无参的调用就会存在二义性出错。
  如果类里面没有显示的构造函数,编译器会自动生成一个无参的,但是没有初始化,而且你还不能调试进去.
  如果定义了,就不会再生成。
- 那么为什么要有这种机制呢?
 
C++里面把类型分成内置类型和自定义类型
内置类型:int char 以及内置类型数组.自定义类型:struct、class定义的类型。
我们不写编译器默认生成的构造函数,对于内置类型不做初始化处理, 对于自定义类型去调用他的默认构造函数(不用参数就可以调用)包括全缺省初始化,如果没有默认构造函数就会报错。
默认构造函数:全缺省,手动写无参的,编译器默认生成的(不处理内置类型成员变量只处理自定义类型成员变量)这三个版本都可以.默认构造函数只能有一个.
class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
    //全缺省
	Date(int y = 1, int m = 1, int d = 1)
	{
		_year = y;
		_month = m;
		_day = d;
	}
    //如果用这个,就不会生成默认构造函数就会导致没有默认构造函数可用而报错
    Date(int y, int m, int d)
	{
		_year = y;
		_month = m;
		_day = d;
	}
};
class A
{
private:
	int _a;
	Date dd;
public:
};
int main()
{
	A a;
 
析构函数
对象在销毁时也就是出了作用域生命周期结束时,自动调用析构函数完成对象的一些资源清理工作
 析构函数名是在类名前面加~
 对象是创建在main函数的栈空间上面的,对象的销毁就是栈帧的销毁,不是析构函数管的
 对象的this指针指向的资源是需要析构函数释放的
 析构的顺序是相反的。
 并不是所有的类都需要写析构函数,所以不实现析构函数也可以.
 系统会自己生成析构函数
 如果我们不写默认生成的析构函数和构造函数类似, 对于内置类型成员变量不作处理,对于自定义类型调用他的析构函数.
 自己动态开辟的资源,都需要自己写析构函数清理资源。避免误杀,所以语言不能设计成自动释放.
class Stack
{
	int* _a;
	size_t _top;
	size_t _cap;
public:
	Stack(int capacity = 4)
	{
		_a =(int*) malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			cout << "malloc failed" << endl;
			return;
		}
		_top = 0;
		_cap = capacity;
	}
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _cap = 0;
	}
};
 
拷贝构造函数
和构造函数构成重载,用一个同类型的对象初始化我.
-  
拷贝构造需要引用
调用拷贝构造,需要先传参数,传值传参又是一个拷贝构造,拷贝构造又需要先传参,但是传值传参还是拷贝构造,无线套娃.
所以当加上引用操作时,形参就是实参的一个别名,就不会调用拷贝构造了,避免递归套娃.-  
首先解决,为什么传值传参是一次拷贝构造?代码调试可知,传参之前,先进行一次拷贝构造,然后再调用f()函数给形参赋值.如果加上&,就直接走f()函数了,因为是一个别名.
class Date { private: int _year; int _month; int _day; public: Date(int y = 1, int m = 1, int d = 1) { _year = y; _month = m; _day = d; } Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day; } ~Date() {} }; void f(Date d)//& {} int main() { Date d(2023,1,7); f(d); } 
 -  
 
如果没有显示定义,系统会生成一个默认的拷贝构造函数.
- 默认生成拷贝构造函数:
 
内置类型的成员,会完成按字节序的拷贝(浅拷贝)所有内容都一样的拷贝给你。有的类就没必要自己写,用默认生成的就可以,比如日期类里面都是内置类型.如果动态开辟了一块空间,那么浅拷贝之后,int *ptr _a地址相同,两个对象指向的都是一块空间,调用个析构函数就会连续释放两次.
深拷贝就得自己实现拷贝构造。自定义类型成员会调用他的拷贝构造。拷贝构造我们不写生成的默认构造函数,对于内置类型和自定义类型都会进行拷贝处理。但是处理细节是不一样的。
运算符的重载
默认C++是不支持自定义类型对象使用运算符的.
 函数名:operator操作符。返回类型看操作符运算之后返回值是什么.
 参数,操作符有几个操作数就有几个参数。
 不能通过连接其他符号来创建新的符号类型
 对于内置类型的运算符不能改变其含义
 .* :: sizeof ?: . 注意以上5个运算符是不能进行重载的
 不能被重载的运算符只有5个, 点号. 三目运算?: 作用域访 问符:: 运算符sizeof 以及.*
赋值运算符的重载
连续赋值,就需要赋值构造有返回值然后向前赋值操作,返回值就涉及到传值返回,还是传引用返回.传值返回会有一份拷贝,调用一次拷贝构造返回。有几个连续赋值操作,就减少了几次因为返回值的拷贝构造次数.
参数类型----返回值----检测是否给自己赋值操作----返回*this----如果我们不写赋值重载会自动生成一个:编译器默认生成的赋值重载和拷贝构造做的事情完全类似:
- 1.内置类型成员,浅拷贝
 - 2.自定义类型会调用operator
 
两个已经存在的对象之间是赋值重载,有一个还没创建的对象就是拷贝构造.
拷贝构造次数编译器优化
class A
{
  public:
    A()
    {}
    A(const A & d)
    {
        cout<<"A(const A & d)"<<endl;
    }
    A& operator=(const A &)
    {
        cout<<"A& operator=(const A &)"<<endl;
        return *this;
    }
};
void F(A a)
{
    return a;
}
int main()
{
    A a;
    A b=f(a);
    //传值的时候,那个对于形参的拷贝,返回值的时候,传值返回又是一次拷贝构造,返回值再赋值给别人b的时候,又是一次拷贝构造,而一次调用里面,连续构造函数会被编译器优化合二为一,将上述二三进行合并。
    
    A c;
    A d;
    d=F(c);
    //如果d是已经存在的对象就无法实现优化了,因为是一次赋值操作,不是连续的构造函数的了.
    
    A();//匿名对象
    	//匿名对象生命周期只在这一行,过了这一行就调用析构函数。同时实现拷贝构造次数的减少,构造和拷贝构造给形参合二为一
    F(A());//传参传匿名对象
}
 
传参和传返回值的连续构造的过程当中,存在优化的过程。接收与否都是存在返回值的,返回值就是生成的临时变量不接受仍然存在一次拷贝构造。传值返回生成的临时变量和匿名对象相当于一回事,同样进行优化。
//下面的例子,拷贝构造是9次但是优化为了7次
class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
	A(const A& a)
	{
		cout << "A(const A & a)" << endl;
	}
};
A f(A a)
{
	A v(a);
	A w = v;
	return w;
}
int main()
{
	A u;
	A y = f(f(u));
	return 0;
}
 

- 返回值出了函数体,不在了,就用传值返回;出了调用的函数体还在,就用引用返回.
 
前置++后置++
重载++,前置+=和后置++都是operator++(),所以需要在后置++时加上int占位,构成函数重载.后置++要多两次拷贝构造,所以对于自定义类型推荐前置++.
帮助理解:前置++,返回的是自身加加之后的值,而且返回的时候自身对象还在,所以是引用返回.后置++是返回的原本的值,但是返回时还得完成++,所以需要拷贝构造一个自己并完成自己本身的加加,返回的是影子,因为影子的作用域仅限于这个函数体,所以是传值返回.
> < != == += + - -=
实现一种,然后根据逻辑关系实现其他时采取复用代码的方式.所有的类涉及到比较大小都可以采取这种复用的方式.
注意:+=,-=可能提供的是负数导致我们的逻辑出错,加上特判,如果是负数就去调用相反的函数即可.
-  
日期相减计算相隔多少天
比较大小之后,小的日期++,加了多少次就是相隔多少天.
 -  
给定日期计算是星期几
1900年的1月1日是星期一,日期相减,然后取模7.
 
const成员

const Date*不能传给Date*类型,涉及到权限的放大,所以C++引入const成员函数,如果在函数Print()后面加了一个const 就可以了.传参的时候就是const Date* const this
成员函数加上const是好的,能加上就都加上,这样普通对象(权限的缩小)和const对象都可以调用。只要不改变都可以加上const,+=之类改变的不能加上.也可以起到保护的作用,如果有代码尝试修改,就会报错.
取地址运算符的重载,不声明也是可以直接用的是默认成员函数。
operator>>&&operator<<
流提取运算符的重载用于自定义类型的使用。
//如果>>重载为成员函数
class Date
{};
int main()
{
    Date d(2022,1,1);
    //cout<<d;//这样是跑不起来的
    //操作符重载里面,如果是双操作数的操作符重载,第一个参数是左操作数,第二个是右操作数.
    d.operator<<(cout);
    d<<cout;//不符合使用习惯和使用含义
}
 
如果不是类里面的成员函数就可以定义成全局函数就可以正常使用,但是这样访问不了私有成员.
为了解决,就引用了友元的,friend设置,友元的声明要放到类的里面。这样处理将就能够访问内置成员变量.类的里面的成员函数默认是第一个是成员变量的this指针,所以放到全局里面使用全局函数,重载的是全局函数.全局函数就和this什么的没关系的.
#include<iostream>
using namespace std;
class Date
{
	friend istream& operator>>(istream& in, Date& d);
	friend ostream& operator<<(ostream& out,const Date& d);
public:
	int GetMonthDay(int year,int month)
	{
		static int arr[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
		int day = arr[month];
		if (month == 2 && ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0)))
		{
			day += 1;
		}
		return day;
	}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		cout << "拷贝构造函数" << endl;
	}
	Date(int year = 1, int month = 1, int day = 1)
	{
		_day = day;
		_month = month;
		_year = year;
		cout << "构造函数" << endl;
	}
	bool operator>(const Date& d)
	{
		if (_year > d._year)
		{
			return true;
		}
		else if (_year == d._year && _month > d._month)
			return true;
		else if (_year == d._year && _month == d._month && _day > d._day)
			return true;
		else
			return false;
	}
	Date operator-(int day)
	{
		Date tmp(*this);
		tmp -= day;
		return tmp;
	}
	Date& operator-=(int day)
	{
		if (day < 0)//客户脑子问题,减一个负数
		{
			return *this += -day;
		}
		_day -= day;
		while (_day <= 0)
		{
			--_month;
			if (_month == 0)
			{
				--_year;
				_month = 12;
			}
			_day += GetMonthDay(_year,_month);
		}
		return *this;
	}
	bool operator==(const Date& d)
	{
		return (_year == d._year) && (_month == d._month) && (_day == d._day);
	}
	bool operator>=(const Date& d)
	{
		return *this > d || *this == d;
	}
	bool operator<(const Date& d)
	{
		return !(*this >= d);
	}
	bool operator!=(const Date& d)
	{
		return !operator==(d);
	}
	//赋值重载
	Date& operator=(const Date& d)
	{
		if (*this != d)//this!=&d,对象比较->地址比较
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
			return *this;
		}
		//cout << "赋值重载" << endl;
	}
	Date& operator+=(int day)
	{
		if (day < 0)
		{
			return *this -= -day;
		}
		_day += day;
		while (_day > GetMonthDay(_year, _month))
		{
			_day -= GetMonthDay(_year, _month);
			_month++;
			if (_month == 13)
			{
				_year++;
			}
		}
		return *this;
	}
	Date operator+(int day)
	{
		Date tmp(*this);
		tmp += day;
		return tmp;
	}
	//++d
	Date& operator++()
	{
		*this += 1;
		return *this;
	}
	//d++ d1.opeartor(&d1,0)
	Date operator++(int)
	{
		Date tmp(*this);
		*this += 1;
		return tmp;
	}
	//日期相减
	int operator-(const Date& d)
	{
		Date max = *this;
		Date min = d;
		int flag = 1;
		if (*this < d)
		{
			flag = -1;
			max = d;
			min = *this;
		}
		int count = 0;
		while (max != min)
		{
			count++;
			++min;
		}
		return count * flag;
	}
	void GetWeekDay(Date& d)
	{
		const char* arr[] = {"周一","周二","周3","周4","周5","周6","周日"};
		Date start(1900,1,1);
		int count = *this - start;
		cout << arr[count] << endl;
	}
private:
	int _day;
	int _month;
	int _year;
};
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}
ostream& operator<<(ostream& out,const Date& d)
{
	out << d._year << " " << d._month << " " << d._day << endl;
	return out;
}
//int main()
//{
//	Date d1(2022,1,3);
//	//d1++;
//	++d1;
//	//d1 += 60;
//	/*d1 -= -69;
//	cout << d1 << endl;*/
//	/*Date ret1 = d1++;
//	cout << ret1 << endl;
//	Date ret2 = ++d1;
//	cout<< ret2 << endl;*/
//
//	//Date d1(2022,1,16);
//	//Date d2(2023, 1, 7);
//	//Date d4 = d2;
//	//Date d5;
//	//d5 = d4 = d1;//连续赋值,就需要赋值构造有返回值然后向前赋值操作
//	//cout << d4 << endl;
//	//cout << d5 << endl;
//	/*Date d6 = d1;
//	cout << d6 << endl;*/
//	//cout << &d6 << " " << &d1 << endl;
//	//cout << &d4 << " " << &d2 << endl;
//	//d1 > d2;
//	//int day = d2 - d1;
//	/*Date d3;
//	cin >> d3;
//	cout << d3;*/
//	return 0;
//}
 
再谈构造函数
初始化列表初始化
- 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
 
const常量初始化只有一次机会就是在定义的时候就赋值,而private下成员变量声明的地方是没有空间接收初始化的.一旦对象进入构造函数完成定义就无法再次赋值,所以引进了初始化列表->每个对象的成员变量定义的地方.
-  
  
以下三个成员的变量必须是初始化列表初始化,他们都必须在定义的地方初始化
const,引用,没有默认构造函数的自定义类型成员变量 
对于其他类型的成员变量,如:_year等在哪里初始化都可以,因为他们没有必须要求在定义的时候就初始化
class A
{
public:
	A(int a)//非默认构造函数
	{
		_a = a;
	}
private:
	int _a;
};
class Date
{
public:
    //初始化列表
	Date(int y, int m, int d,int i)
		:_year(y)
		, _month(m)
		, _day(d)
		, _N(10)
		,_ref(i)
		,a(-1)//没有默认构造函数可用就在这里显示的调用
	{}
private:
	int _day;
	int _year;
	int _month;
	const int _N;//声明
	int& _ref;
	A a;
};
int main()
{
	//Date d1(2023,1,13);//对象定义
	Date d1(2023,1,13,10);
	return 0;
}
 
- 不使用初始化列表
 

-  
使用初始化列表

 
所以,内置类型的成员,在函数体和初始化列表初始化都可以的,但是自定义类型推荐在初始化列表初始化
-  
成员变量在类中的声明次序是其在初始化列表的初始化顺序,其实空间到这里都已经开好了.

 
explicit 禁止隐士类型转换
C语言中的隐式类型转换:相近类型-意义相似的类型
 这个过程中会构造一个临时变量,在将这个临时变量拷贝构造给前面那个.

自定义类型支持单参数的构造函数,这样一个整形就可以创建一个临时对象C(10),在赋值给前面那个.
C++编译器在连续的一个过程中,多个构造会被优化,直接优化为一个构造就行,
- 所以为了避免隐士类型转换的发生引入explicit。在构造函数前面加上explicit.
 
引用临时变量具有常性,所以前面的要加上const。
static成员
类里面声明静态成员变量属于整个类属于所有对象,生命运行周期在整个程序运行期间.需要在类的外面初始化,和这个类深度绑定.如果不是静态的就和每个对象绑定就没意义了.
-  
一个类到底创建了多少个对象?
采用静态成员变量进行计数器
 
class D
{
public:
	D(int d)
	{
		_d = d;
		++_scount;
	}
	D(const D& d)
	{
		_d = d._d;
		++_scount;
	}
	//没有this指针,只能访问静态的成员变量和成员函数
	static int GetCount()
	{
		return _scount;
	}
private:
	int _d;
	static int _scount;//类里声明
};
int D::_scount = 0;//类外定义
int main()
{
	D d1(10);
	D d2(d1);
	D d3 = 1;
	//类外访问
		//只能是公有的情况
	/*cout << D::_scount << endl;
	cout << d1._scount << endl;
	cout << d2._scount << endl;*/
		//私有的情况
	cout << d1.GetCount() << endl;
	cout << D::GetCount() << endl;
}
 
- 使用静态成员变量计算1+…+n的和
 
class Sum
{
public:
    Sum()
    {
        ret+=i;
        ++i;
    }
    static int GetRet()
    {
        return ret;
    }
private:
    static int ret;
    static int i;
};
int Sum::ret=0;
int Sum::i=1;
class Solution
{
    int GetSum(int n)
    {
        Sum a[n];//支持变长数组
        return Sum::GetRet();
    }
};
 
static限制对象具有文件域,只能在当前.cpp文件所在的编译模块中使用。
 静态成员函数没有this 指针,只能访问静态成员变量和成员函数 。
 静态成员变量只能在类外面初始化
 static成员函数只能调用静态变量和其他static函数
 static成员变量在对象生成之前生成。
C++初始化成员新玩法-缺省值
因为默认构造函数对于自定义类型不处理,新玩法就是给的是成员变量缺省值,声明的时候给个缺省值.这里不是初始化而是缺省值.如果在初始化列表阶段没有对成员变量初始化,他就用缺省值初始化.
class F
{
public:
	F(int f)
	{
		_f = f;
	}
private:
	int _f;
};
class E
{
public:
	E()
	{}
private:
	int _e1 = 0;
	F _f1 = 10;
	F _f2 = F(20);
	int* p = (int*)malloc(4*10);
	int arr[10] = {1,2,3,4,5,6};
};
 
静态的成员变量不能给缺省值,必须在类外面初始化
友元
友元函数 和友元类:在一个普通函数当中访问对象的私有变量
 突破封装访问私有private里面的成员,友元就像是黄牛一样,破坏了管理规则
 友元是单向的,我是你的友元,我就可以访问你的。
 友元函数不具备this指针,全局变量也不具备this指针
 友元函数相当于全局函数可以直接调用
 友元函数不可以继承和传递。
内部类
内部类:在一个类里面再定义一个类
 内部类和在全局定义的类基本一样,只是他受外部类类域的限制
 内部类天生就是外部类的友元,也就是B中可以访问A的私有.但是外部类不能访问内部类的私有.当在内部类添加友元之后外部类才可以访问.
this注意
- 基类保护成员在子类可以直接被访问,跟this无关
 - 基类私有成员在子类中不能被访问,跟this无关
 - 基类共有成员在子类和对象外都可以直接访问,跟this无关
玩法-缺省值 
因为默认构造函数对于自定义类型不处理,新玩法就是给的是成员变量缺省值,声明的时候给个缺省值.这里不是初始化而是缺省值.如果在初始化列表阶段没有对成员变量初始化,他就用缺省值初始化.
class F
{
public:
	F(int f)
	{
		_f = f;
	}
private:
	int _f;
};
class E
{
public:
	E()
	{}
private:
	int _e1 = 0;
	F _f1 = 10;
	F _f2 = F(20);
	int* p = (int*)malloc(4*10);
	int arr[10] = {1,2,3,4,5,6};
};
 
静态的成员变量不能给缺省值,必须在类外面初始化
友元
友元函数 和友元类:在一个普通函数当中访问对象的私有变量
 突破封装访问私有private里面的成员,友元就像是黄牛一样,破坏了管理规则
 友元是单向的,我是你的友元,我就可以访问你的。
 友元函数不具备this指针,全局变量也不具备this指针
 友元函数相当于全局函数可以直接调用
 友元函数不可以继承和传递。
内部类
内部类:在一个类里面再定义一个类
 内部类和在全局定义的类基本一样,只是他受外部类类域的限制
 内部类天生就是外部类的友元,也就是B中可以访问A的私有.但是外部类不能访问内部类的私有.当在内部类添加友元之后外部类才可以访问.
this注意
- 基类保护成员在子类可以直接被访问,跟this无关
 - 基类私有成员在子类中不能被访问,跟this无关
 - 基类共有成员在子类和对象外都可以直接访问,跟this无关
 - this指针代表了当前对象,能够区分每个对象的自身数据.
 



















