IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【C过渡到C++】-- 类和对象(下) -> 正文阅读

[C++知识库]【C过渡到C++】-- 类和对象(下)

目录

结构函数再探

构造函数体赋值

构造函数初始值有时必不可少

初始化列表

初始化列表存在缘由

初始化列表使用

初始化列表中初始化顺序

explicit关键字

C++11中成员初始化的补丁

static成员

友元

友元类

内部类

类与对象的便利与封装


结构函数再探

构造函数体赋值

????????在创建对象时,编译器通过调用构造函数,给对象中各个成员变量合适的初始值。

class Date
{
public:
	Date(int year = 2022, int month = 9, int day = 26)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

? ? ? ? 注意:以上代码在构造函数所执行的语句只能将其称为赋初值。我们需要区分初始化和赋值之间的差异:

  1. 初始化是只能执行一次的。
  2. 赋值是能在构造函数体内可以多次执行
//对于赋值
class Date
{
public:
	Date()
	{
		_year = 2022; //赋初值
		_year = 2021; //再次赋值
		_year = 2020; //第三次赋值
		/*
			.
			.
			.
		*/
		_year = 1; //第n次赋值
	}
private:
	int _year;
};

构造函数初始值有时必不可少

? ? ? ? 有时我们是可以忽略数据结构成员初始化和赋值之间的差异,但并非总是能够如此。如果成员时const或是引用修饰的话,就必须初始化。类似的,当成员属于某种类类型且该类没有定义默认构造函数时,就必须将其成员进行初始化。

一、const修饰的变量:

????????值是不允许改变的,即不允许给它重新赋值,即使是赋相同的值也不可以。 并且const修饰的变量在定义的时候就给它赋初值,否则报错。

二、引用修饰的变量:

????????在初始化时引用一个实体后,就不能再引用其他实体,即不允许给它重新赋值,在定义时必须初始化,否则报错。

class Date
{

public:

	Date()
	//error:_year与p必须被初始化
	{
		_i = 0;		    //true
		_year = 2022;   //error:不能给cosnt赋值(即:左值不能更改)
		_p = _i;		//error:_p未被初始化
	}
private:
	int _i = 0;
	const int _year;
	int& _p;
};

????????和其他的常量对象和引用对象一样,对象_year与_都必须被初始化。因此,如果我们没有为它们提供构造函数初始值的话,将会引发error。

三、没有定义默认构造函数的类:

????????在构造函数中的调用,是该变量已经进行了定义,在构造函数中的语句,只能称作为在已初始化的情况下,再次进行赋值。而没有默认构造函数,即编译器无法自行进行初始化。

在这里提醒一下,默认构造函数是指不用传参就可以调用的构造函数:
?1.我们不写,编译器自动生成的构造函数。
?2.无参的构造函数。
?3.全缺省的构造函数。

(默认构造函数只能纯在一个)

class A 
{
public:
	//没有默认构造函数
	A(int a) { _a = a; }
private:
	int _a;
};
class B {
public:
	B(int a)
	//_aobj必须被初始化
	{
		_aobj = a; //error:没有合适的默认构造函数可用
	}
private:
	A _aobj; // 没有默认构造函数
};
Note:如果成员是const、引用,或者是属于某种未提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些成员提供初始值。

初始化列表

初始化列表存在缘由

? ? ? ?可以将初始化列表理解为就是成员变量定义的地方。

class Date
{
public:
	Date(int year, int month)
	{
		_year = year;
		_month = _month;
	}
private :
	int _year;   //成员的声明
	int _month;  //成员的声明
};
int mian()
{
	Date d(2022, 9); //Date对象整体的定义 -- 而日期类对象里面还有成员
	return 0;
}

? ? ? ? C++就是通过初始化列表确定对象内成员的定义。不管我们写不写都会有初始化列表,如果写了就用写的,没有写还是会认为有初始化列表,只不过初始化列表对于没有默认构造函数的自定义类型不初始化,对内置类型才会进行初始化,内置类型定义了但是没有给值,是随机值。

初始化列表使用

????????初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。

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. 自定义类型成员(且该类没有默认构造函数时)
class A 
{
public:
// 没有默认构造函数
 A(int i)
 :_i(i)
 { }

private:
 int _i;
};

class B 
{
public:
 B(int a, int p)
 :_a(a)
 ,_p(p)
 ,_n(10)
 { }

private:
 A _a;         // 没有默认构造函数
 int& _p;      // 引用
 const int _n; // const 
};
??? ?????初始化类的成员有两种方式:1. 是使用初始化列表;2. 是在构造函数体内进行赋值操作。除必须使用初始化列表的情况外,对二者就没有了必要的要求,即在很多类中,初始化和赋值的区别就是事关效率问题:前者直接初始化数据成员,后者则先初始化再赋值。
如:
????????自定义类型成员(且该类有默认构造函数时)
class Time
{
public:
    //默认构造函数
	Time(int time = 0)
		:_time(time)
	{ }
private:
	int _time;
};
class Date
{
public:
	Date()
	{
		Time t_tmp = (20); //多调用了一次默认构造函数
		t = t_tmp;
	}
private:
	int _year;
	Time t;
};

重点提一句:尽量使用初始化列表初始化

? ? ? ? 1.?对于内置类型的变量:使用初始化列表和在构造函数体内进行初始化其实并没多大的消耗,小到是完全可以忽略的。可以简易的理解为:

// 在构造函数体内进行赋值(不使用初始化列表)
int num;
num = 10;

// 使用初始化列表
int num = 10;

? ? ? ? 2. 对于自定义类型的变量:使用初始化列表只需一次调用默认构造函数的过程,这对于数据密集型的类来说,是非常高效的。

class Time
{
public:
    //默认构造函数
	Time(int time = 0)
		:_time(time)
	{ }
private:
	int _time;
};
class Date
{
public:
	Date()
        :t(20); //只需一次调用默认构造函数的过程
	{ }
private:
	int _year;
	Time t;
};

初始化列表中初始化顺序

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

class A 
{
public:
	A(int a)
        //与其放置的顺序是无关的
		:_a2(a)   //第二个执行 --> a
		,_a1(_a2) //第一个执行 --> 随机值
	{ }
	void Print() { cout << _a1 << " " << _a2 << endl; }
private:
	int _a1;
	int _a2;
};
int main() 
{
	A a(1);
	a.Print();
}

explicit关键字

? ? ? ?? 构造函数不仅可以构造与初始化对象?,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。?
class A
{
public:
	A(int a = 0) //单个参数构造函数
		:_a(a)
	{
		cout << "A(int a = 0)" << endl; //构造函数调用的次数
	}
	A(const A& a)
		:_a(a._a)
	{
		cout << "A(const A& a)" << endl; //拷贝构造函数调用的次数
	}
private:
	int _a;
};
int main()
{
	A a1(1);  //直接调用构造函数
	A a2 = 1; //隐式类型的转换:构造函数 + 拷贝构造 + 编译器优化 --> 直接调用构造函数
    //隐式准换相当于:
    //A tmp(1); //先构造
    //A a2(tmp); //再拷贝构造
	
    return 0;
}

? ? ? ? 执行效率上a1与a2是相同的,它们所执行的都是直接构造函数,而编译器的底层执行是不同的。

class A
{
public:
	A(int a = 0) //单个参数构造函数
		:_a(a)
	{
		cout << "A(int a = 0)" << endl; //用于验证构造函数调用的次数
	}
private:
	int _a;
};
int main()
{
	//验证方式一:引用
	A& a3 = 1; //隐式类型的转换:生成临时变量,而临时变量是具有常性的 --> error:a3未用const修饰(无法从“int”转换为“A &”)
	return 0;
}
class A
{
public:
	//验证方式二:explicit
	explicit A(int a = 0) //单个参数构造函数
		:_a(a)
	{ }
private:
	int _a;
};
int main()
{
	A a1(1);  //直接调用构造函数 --> 正常运行
	A a2 = 1; //隐式类型的转换 --> error:无法从“int”转换为“A &”
	return 0;
}

? ? ? ? 其与我们平时所接触的内置类型隐式转换是类似的:

int i_num = 10;
double d_num = i_num; //隐式类型转换

? ? ? ? 即explicit的用于修饰构造函数,加以阻止单参构造函数的隐式转换。关键字explicit只对一个实参的构造函数有效。需要多个实参的构造函数不能用于执行隐式转换,所以无需将这些构造函数指定为explicit的。只能在类内声明构造函数时使用explicit关键字,在类外部定义时不应重复。

//error:explicit关键字只允许出现在类内的构造函数声明处
explicit A::A(int a)
{ _a = a; }

C++11中成员初始化的补丁

????????C++11支持非静态成员变量在声明时进行初始化赋值,需要注意的是,此处的书写方式如同我们平时所写的定义并初始化,但是这里不是一样的,这里是给声明的成员变量一个缺省值

class Date
{
public:
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year = 2022;
	int _month = 10;
	int _day = 1;
};
int main()
{
	Date d;
	d.Print();
}

static成员

? ? ? ? 有时候类需要它的一些成员与类本身直接相关,而不是与类的各个对象保持关联。从实现效率的角度来看,没必要每个对象都进行存储。是每个类对象所共享,不属于某个具体的对象

????????通过在成员之前加上关键字static使得其可以与类关联,和其他成员一样,其可以是public或private的。声明为static的类成员称为类的静态成员,用static修饰的成员变量(常量、引用、指针、类类型等),称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。

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

class A
{
private:
	static int _a;
};
int main()
{
	cout << sizeof(A) << endl; //输出为1,因为静态成员_a是存储在静态区,并为存储与类的空间中
	return 0;
}

????????计算类的大小或是类对象的大小时,静态成员存储于静态区,是属于所有类的同类类型的,是不计入总大小之和的。

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

class A
{
private:
	static int _a; //声明
};
int A::_a = 10; //静态成员变量一定要在类外进行初始化 -- 添加static -> 即error
三、类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问。
class A
{
public:	//需要在公有的情况下,类外才可以使用类中的成员变量
	static int _a;
};
int A::_a = 10; 
int main()
{
	A a;
	cout << a._a << endl;   //类对象.进行访问
	cout << A()._a << endl; //匿名对象.进行访问
	cout << A::_a << endl;  //类名::进行访问
	return 0;
}
四、静态成员也是类的成员,受public、protected、private 访问限定符的限制。
class A
{
public:
	static int _a1;
private: //protected:
	static int _a2;
};

int A::_a1 = 10;
int A::_a2 = 10;

int main()
{
	cout << A::_a1 << endl; //正确
	cout << A::_a2 << endl; //error:无法访问
	return 0;
}

静态成员函数

????????静态成员函数主要为了调用方便,不需要生成对象就能调用。 静态成员函数的作用类似于:一个在命名空间中的全局函数。
class A
{
public:
	static void Func1()
	{
		Func2(); //没有this指针,无法准换为this->Fun2();
	}

	void Func2()
	{
		cout << "void Func2()" << endl;
	}
};

int _a = 10;

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

????????它跟类的实例无关,只跟类有关,不需要this指针。
class A
{
public:
	static void Func1()
	{
		_a = 20;
		_b = 10; //error:无法转换为this->_b
	}
private:
	static int _a;
	int _b;
};
int _a = 10;
【问题】
  • 静态成员函数可以调用非静态成员函数吗?
??????? ?不可以。因为非静态成员函数有第一个形参,且默认为this指针,而静态成员函数中是没有this指针的。即,静态成员函数不可调用非静态成员函数。
class A
{
public:
	static void Func1()
	{
		Func2(); //error:没有this指针,无法准换为this->Fun2();
	}

	void Func2()
	{
		cout << "void Func2()" << endl;
	}
};
int _a = 10;
  • 非静态成员函数可以调用静态成员函数吗?
???????? 可以。因为静态成员函数和非静态成员函数都在类中,而在类中不受访问限定符的限制。
class A
{
public:
	static void Func1()
	{
		cout << "static void Func1()" << endl;
	}

	void Func2()
	{
		Func1(); //正确:有this指针,可以转换为this->Func1;
	}
};
int _a = 10;

友元

? ? ? ? 这就要提到关于自定义类的<<与>>运算符的运算符重载实现了。按照普通的运算符重载实现会发现一些问题:

class Date
{
public:
	ostream& operator<<(ostream& _cout) //第一个参数一定为this,在类内无法改变顺序
	{
		_cout << _year << "-" << _month << "-" << _day << endl;
		return _cout;
	}
private:
	int _year = 2022;
	int _month = 10;
	int _day = 1;
};
int main()
{
	Date d;
	//不符合我们平时所用的顺序
	d << cout; //因为要与成员函数的参数位置所对应,左参数在第一个,右参数在第二个
	return 0;
}
? ? ? ? cout的使用方式会与日常所用方式不同。第一个参数是this指针,是没有办法的,所以在内中书写是不可行的了。
???????? 类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为它的友元。如果类想把一个函数作为它的友元,只需要增加一条以friend关键字开始的函数声明语句即可。友元函数可以直接访问类的私有成员,它是定义在类外部普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

一、友元函数可访问类的私有和保护成员,但不是类的成员函数,友元函数可以在类定义的任何地方声明,不受类访问限定符限制。

class Date
{
    // 友元函数的声明
	friend ostream& operator<<(ostream& out, const Date& d);

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

private:
	int _year;
	int _month;
	int _day;
};
// 友元函数的定义
ostream& operator<<(ostream& out, const Date& d)
{
	cout << d._year << "-" << d._month << "-" << d._day << endl;
	return out;
}
int main()
{
	Date d;
	cout << d;
	return 0;
}

二、友元函数不能用const修饰。

class Date
{
public:
	// 友元函数的声明
	friend ostream& operator<<(ostream& out, const Date& d)const; //error:非成员函数上不允许使用类型限定符 -- 没有this指针
private:
	int _year;
	int _month;
	int _day;
};
// 友元函数的定义
ostream& operator<<(ostream& out, const Date& d)const //error:非成员函数上不允许使用类型限定符 -- 没有this指针
{
	cout << d._year << "-" << d._month << "-" << d._day << endl;
	return out;
}

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

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

友元类

?????? ??友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
  • 友元关系是单向的,不具有交换性。即:类A是类B的友元,那么A可以访问B的私有成员变量,但B不可以访问A的私有成员变量。
  • 友元关系不能传递。 即:A是B的友元,B是C的友元,则不能说明A是C的友元。
class B
{
	friend class A;
private:
	int _b;
};
class A
{
public:
	A()
	{
		_a = 10;
		b._b = 10; //A是B的友元,即:A可以访问B的私有变量
	}
private:
	int _a;
	B b;
};
int main()
{
	A a;
	return 0;
}

【注意】

????????类的一大特点就是对成员的封装,而友元的使用让类外函数可以访问私有成员,友元破坏了类的封装,所以,要减少对友元的使用

内部类

【概念】

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

【注意】
???????? 内部类就是外部类的友元类。
  • 内部类可以定义在外部类的publicprotectedprivate都是可以的。
  • 内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
  • sizeof(外部类)=外部类,和内部类没有任何关系。
class A {
public:
     class B //B天生就是A的友元,不需要friend
     {
     public:
         void foo(const A& a)
         {
             cout << k << endl; //正确
             cout << a.h << endl; //正确
         }
     };
private:
    static int k;
    int h;
};


int A::k = 1;

int main()
{
    A::B b;
    b.foo(A());
 
    return 0;
}

类与对象的便利与封装

?????? ??在类和对象阶段,要体会到:类是对某一类实体(对象)来进行描述的,描述该对象具有那些属性,那些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化具体的对象
? ? ? ? 而类与对象好处也在于便利性与封装性:就如同一个银行。

? ? ? ? 便利性:你只需要提出你的要求,而选项也是有一定规定的,就例如取钱,存钱,贷款等。对应,默认的六大函数,在类中,我们将其进行规定,但是规定的也只是执行形式,主要的执行意义是一定的,存钱、创建;取钱、析构。类中就如同银行内部,每个部门类似对应成员函数,功能齐全,类外的人无需关心内部的执行方式,只需要按照自己的方式使用。假如需要使用,银行你需要去往特定的窗口,而类外需使用对应的接口。

???????? 封装性: 在银行中,银行是不会可能允许用户随意进入厂库,毕竟谁敢保证你会不会乱搞,比如拿些钱。同样的类中有私有与共有,只允许让你访问其允许的,这是类对于自己的一种保护,正式因为此,友元的存在其实是不友好的,所以要减少对于友元的使用。
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-10-08 20:22:11  更:2022-10-08 20:23:21 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 2:50:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码