类和对象(上)

本文介绍了C++中的引用概念,强调了引用是变量的别名,权限不能扩大。接着讨论了类和对象的构造函数,包括默认构造函数、析构函数和拷贝构造函数的作用。还涉及了运算符重载,如赋值运算符和自增运算符的重载策略。此外,文章提到了初始化列表、显式关键字以及静态成员的使用。
摘要由CSDN通过智能技术生成

“>>”:流提取运算符

“<<”:流插入运算符

引用

取别名的过程中,权限可以缩小但是不能够放大。

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*)占用的字节数相同.

image-20230107100935850

类和对象

兼容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在指针类型之后,是指针不可更改,指向的对象的自身内容是可以更改的。

image-20230107120056075

  • 违反语法是编译报错。检查出空指针是运行时错误。

    空指针调用成员函数不会编译报错,因为这不是语法错误,编译器检查不出来。

    也不会出现空指针访问,因为调用成员函数没有存在对象里面,在一个公共代码区.会把实参传给隐藏的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;
}

image-20230109110539664

  • 返回值出了函数体,不在了,就用传值返回;出了调用的函数体还在,就用引用返回.
前置++后置++

重载++,前置+=和后置++都是operator++(),所以需要在后置++时加上int占位,构成函数重载.后置++要多两次拷贝构造,所以对于自定义类型推荐前置++.

帮助理解:前置++,返回的是自身加加之后的值,而且返回的时候自身对象还在,所以是引用返回.后置++是返回的原本的值,但是返回时还得完成++,所以需要拷贝构造一个自己并完成自己本身的加加,返回的是影子,因为影子的作用域仅限于这个函数体,所以是传值返回.

> < != == += + - -=

实现一种,然后根据逻辑关系实现其他时采取复用代码的方式.所有的类涉及到比较大小都可以采取这种复用的方式.

注意:+=,-=可能提供的是负数导致我们的逻辑出错,加上特判,如果是负数就去调用相反的函数即可.

  • 日期相减计算相隔多少天

    比较大小之后,小的日期++,加了多少次就是相隔多少天.

  • 给定日期计算是星期几

    1900年的1月1日是星期一,日期相减,然后取模7.

const成员

image-20230109111925597

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;
}
  • 不使用初始化列表

image-20230113100835562

  • 使用初始化列表

    image-20230113101315220

所以,内置类型的成员,在函数体和初始化列表初始化都可以的,但是自定义类型推荐在初始化列表初始化

  • 成员变量在类中的声明次序是其在初始化列表的初始化顺序,其实空间到这里都已经开好了.

    image-20230113101914458

explicit 禁止隐士类型转换

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

image-20230113103852183

自定义类型支持单参数的构造函数,这样一个整形就可以创建一个临时对象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指针代表了当前对象,能够区分每个对象的自身数据.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值