栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++:流插入与流提取的运算符重载,explicit关键字,静态成员变量与非静态成员变量用法 详解

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++:流插入与流提取的运算符重载,explicit关键字,静态成员变量与非静态成员变量用法 详解

目录

一.流插入<<与流提取>>

1.他们能自动识别内置类型的原因:函数重载

2.重载日期类的流插入 cout <<

(1)<<重载函数 不能写在类中,原因:

(2)<<重载函数 写在类外 要搭配 友元

(3)<<重载函数 写在类外不能写在头文件中

那类是在.h文件,类中定义的函数会重定义吗?

(4)为支持连续流插入需要设置返回值(完善版本)

3.流提取>> (和流插入<<搭配)

二.初始化列表

1.定义

2.格式

3.特征

(1)例子1:都初始化

 (2)例子2:缺省值和初始化列表

 (3)对特征4.的解释

三.explicit关键字

1.用途:

2.构造函数的隐式类型转换

(1)解释拷贝构造 Date d2 = 2022;  

(2)解释 const Date& d6=2022 为什么加const

(3)构造函数的隐式类型转换的语法的意义

四.static成员

1.概念

2.使用的例子:

3.静态成员变量和静态成员函数的特征

4.静态成员的访问形式

(1)静态成员变量公有时:

 (2)静态成员变量私有时:

静态成员函数特征:

五.C++11 的成员初始化新玩法。

1.C++11支持非静态成员变量在声明时进行初始化赋值

有以下三种写法:

静态成员变量不可以给缺省值原因:

2.静态成员变量的OJ题:JZ64 求1+2+3+...+n


一.流插入<<与流提取>>

1.他们能自动识别内置类型的原因:函数重载

重载了各种内置类型的<<运算符重载函数,所以遇到不同内置类型时,会自动匹配对应的函数重载

cout类型是ostream(out)

cin类型是istream(in)

2.重载日期类的流插入 cout <<
Date.h:
#include 
#include 
using std::cout;
using std::cin;
using std::endl;

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
    {
	_year = year;
	_month = month;
	_day = day;
    }
	void operator<<(std::ostream& out)    //cout的类型是ostream
	{
		out << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};


test.cpp:
void TestDate7()
{
	Date d1;
	d1.operator<<(cout);
	d1 << cout;            //重载写法颠倒了,虽然能正常运行,但格式很难看,正常应该是cout< 

(1)<<重载函数 不能写在类中,原因:

如果要把 <<函数重载 写在日期类中,日期类默认有this指针,调用void operator<<(std::ostream& out)时因为是双目运算符,第一个参数默认传给this指针,第二个参数传给形参,所以调用时只能写成d1 << cout; 但正常的写法应该是 cout<

(2)<<重载函数 写在类外 要搭配 友元

如果把<<函数重载 写在类外 ,那他将无法访问对象的私有成员变量_year等,所以要在类中加上友元,友元:表明这个外部函数是类的朋友,可以随意访问私有成员变量。但这仍然有问题

(这样仍是存在问题的,未完善)
Date.c: 

class Date
{
	friend void operator<<(std::ostream& out, const Date& d1);

public:
	Date(int year = 1, int month = 1, int day = 1);
	
private:
	int _year;
	int _month;
	int _day;
};
void operator<<(std::ostream& out,const Date& d1)
{
	out << d1._year << "-" << d1._month << "-" << d1._day << endl;
}

(3)<<重载函数 写在类外不能写在头文件中

若写在头文件中,Date.cpp和test.cpp都包含了Date.h,void operator<<(std::ostream& out,const Date& d1) 将会在两个cpp文件展开定义,编译生成两个.o文件,Date.o和test.o的符号表都有这个函数的定义,链接时会冲突,重定义。

那类是在.h文件,类中定义的函数会重定义吗?

答:不会,类中成员函数默认是内联,函数定义不放在头文件中,调用时因为是内联,有函数定义,call指令后面就会有函数的地址,所以就不用链接时去找函数地址,并且有函数定义就根本不用把函数放在符号表里;有函数声明时,call指令后面没有函数地址,只有做过处理的函数名,链接时通过函数名去符号表找函数地址,这样也是可以的。

重定义的解决方案:把<<重载函数定义写在Date.cpp中,Date.h展开就只有函数声明了,call指令后面没有函数地址,只有做过处理的函数名,链接时通过函数名去符号表找函数地址。

(仍需要完善)
Date.c: 

class Date
{
	friend void operator<<(std::ostream& out, const Date& d1);

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

Date.cpp:

#include "Date.h"

Date::Date(int year , int month , int day )
{
	_year = year;
	_month = month;
	_day = day;
}
void operator<<(std::ostream& out, const Date& d1)
{
	out << d1._year << "-" << d1._month << "-" << d1._day << endl;
}

(4)为支持连续流插入需要设置返回值(完善版本)
完善版本:
Date.c: 

class Date
{
	friend std::ostream& operator<<(std::ostream& out, const Date& d1);

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

Date.cpp:

#include "Date.h"
Date::Date(int year , int month , int day )
{
	_year = year;
	_month = month;
	_day = day;
}
std::ostream& operator<<(std::ostream& out, const Date& d1)
{
	out << d1._year << "-" << d1._month << "-" << d1._day << endl;
    return out;
}

test.cpp:

void TestDate7()
{
	Date d1,d2;
	cout << d1 << d2;
	operator<<(cout, d1);
}
int main()
{
	TestDate7();
	return 0;
}

3.流提取>> (和流插入<<搭配)

 流提取>>类似流插入,只是因为要把值输入到d1中,所以d1不能带const 这一点差别

Date.h:

#include 
#include 
using std::cout;
using std::cin;
using std::endl;

class Date
{
    //流插入<<
	friend std::ostream& operator<<(std::ostream& out, const Date& d1);
    //流提取>>
	friend std::istream& operator>>(std::istream& in, Date& d1);   

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

Date.cpp

#include "Date.h"
Date::Date(int year , int month , int day )
{
	_year = year;
	_month = month;
	_day = day;
}
std::ostream& operator<<(std::ostream& out, const Date& d1)
{
	out << d1._year << "-" << d1._month << "-" << d1._day << endl;
	return out;
}
std::istream& operator>>(std::istream& in, Date& d1)
{
	in >> d1._year >> d1._month >> d1._day;
	return in;
}

test.cpp

void TestDate7()
{
	Date d1,d2;
	cin >> d1 >> d2;
	cout << d1 << d2;
}
int main()
{
	TestDate7();
	return 0;
}

二.初始化列表

引入:

在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
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.格式

格式: 成员变量() 

 成员变量=0;这样直接赋值是错的,会报错

int value = 10;
class Date
{
public:
		Date(int year, int n, int a)
		:_n(n)
		, _ref(value)
		, _year(year)
		, _aa(a)
	{}
private:
	int _year; 
	const int _n;
	int& _ref;
	A _aa;
};

int main()
{
	Date d1(2022, 1, 10); // 对象定义

	return 0;
}

3.特征 1. 每个成员变量在初始化列表中 只能出现一次 ( 初始化只能初始化一次 ) 2. 类中包含以下成员,必须放在初始化列表位置进行初始化:         ①引用成员变量         ②const成员变量         ③自定义类型成员( 该类没有默认构造函数 )

其他的变量既可以在初始化列表初始化,也可以在函数体内初始化

3. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。 4. 成员变量 在类中 声明次序 就是其在初始化列表中的 初始化顺序 ,与其在初始化列表中的先后次序无关 总结:初始化列表可以认为就是对象的成员变量定义的地方,建议尽量在初始化列表初始化,在函数体定义可能会报错或者效率不高等问题。

(1)例子1:都初始化

初始化d1需要把初始化列表走一遍进行初始化

int value = 10;
class A
{
public:
	A(int x =0)
		:_x(x)
	{}
private:
	int _x;
};

class Date
{
public:
		Date(int year, int n, int a)
		:_n(n)
		, _ref(value)
		, _year(year)
		, _aa(a)
	{}

private:
	// 定义时初始化
	// 也可以定义时不初始化,后面再赋值修改
	int _year; // 声明

	// 只能在定义初始化
	const int _n;
	int& _ref;
	A _aa;
};

int main()
{
	Date d1(2022, 1, 10); // 对象定义

	return 0;
}

 (2)例子2:缺省值和初始化列表
class Stack
{
public:
	Stack(int capacity = 0)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		assert(_a);
		_top = 0;
		_capacity = capacity;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};

class MyQueue
{
public:
	MyQueue(int size = 1)
		:_size(size)
	{}

	
//拷贝构造也是构造函数,你写了拷贝构造就不会生成默认构造函数,所以MyQueue无可以使用的
//默认构造函数,就会报错
private:
	Stack _st1;
	Stack _st2;
	size_t _size = 0; // 缺省值
};
int main()
{
	MyQueue mq;

	return 0;
}

 初始化时会把初始化列表走一遍,挨个初始化成员变量,在初始化列表中先调用_st1和_st2的默认构造函数把他们初始化,再看初始化列表有_size(size),所以就把_size赋成1,如果初始化列表啥也没有,就把_size赋成缺省值0,

总结就是:初始化列表有值就给值 缺省值就没用了,初始化列表没值就给缺省值。如果缺省值和初始化列表都没给值,那对内置类型不做处理,_size为随机值。(缺省值还是属于在初始化列表初始化的)

 (3)对特征4.的解释

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

看一个选择题:

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。初始化列表中的初始化顺序要看成员变量在类中声明次序,与其在初始化列表中的先后次序无关。所以在初始化列表中先初始化_a2,再初始化_a1,选D。

三.explicit关键字

1.用途:

用explicit修饰构造函数,将会禁止单参构造函数的隐式转换。

2.构造函数的隐式类型转换
class Date
{
public:
	Date(int year)        //构造函数
		:_year(year)
	{
		cout << "Date(int year)" << endl;
	}

	

    Date(const Date& d)     //拷贝构造函数
	{
		cout << "Date(const Date& d)" << endl;
	}
private:
	int _year;
};

int main()
{
	int i = 0;
	const double& d = i;

	Date d1(2022);   // 调用构造函数
	// 隐士类型的转换 
	Date d2 = 2022;  // 1.构造 + 拷贝构造 -> 优化 合二为一
	Date d3(d1);     // 2.调用拷贝构造函数
	Date d4 = d1;    // 3.调用拷贝构造函数

	return 0;
}

(1)解释拷贝构造 Date d2 = 2022;  

本质是先隐式类型转换:先隐式类型转换本质又是生成一个中间的临时变量,int 类型的2022构造 Date 类型的临时变量,临时变量再拷贝构造给d2。原本是先构造再拷贝构造,通过编译器优化就成了直接构造。

 若给构造函数加上explicit,将会禁止构造函数的隐式转换,Date d2 = 2022;  就会报错

(2)解释 const Date& d6=2022 为什么加const

构造函数隐式类型转换,2022先构造一个临时变量,然后让d6成为这个临时变量的引用,但是临时变量具有常性,所以必须加上const才正确。

(3)构造函数的隐式类型转换的语法的意义 让后面学习中的使用更加灵活,比如库中有一个类叫string类:
#include

//库中的string类
//class string
//{
//public:
//	string(const char* str)
//	{}
//};

void Func(const std::string& s)
{

}

int main()
{
	string s2 = "hello";

	string s1("hello");     //正常情况是构造一个对象,再把对象传参,但是利用构造函数隐式类型-
	Func(s1);               //-转换的语法可以写成下面的写法

	Func("hello");    //把字符串直接传参就很灵活

	return 0;
}

四.static成员

1.概念

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化

2.使用的例子: 统计A类型对象创建了多少个?构造和拷贝构造各调用了多少次? 首先我们想到的就是在构造和拷贝构造里面分别设置全局变量 count1 和 count2 计数即可,但是将全局变量写在头文件中时,头文件在各个文件中展开会引发冲突,所以不能用全局变量计数了,我们可以用静态的成员变量计数即,static修饰的成员变量 _count1 和 _count2。(完整调用在第4点静态成员的访问形式)
//int count1 = 0;
//int count2 = 0;
class A
{
public:
	A()
	{
		++_count1;
	}

	A(const A& aa)
	{
		++_count2;
	}

private:
	// 静态成员变量属于整个类,属于类的所有对象
	static int _count1;
	static int _count2; // 声明

	int _a;
};

3.静态成员变量和静态成员函数的特征 1. 静态成员为所有类对象所共享,不属于某个具体的对象(计算类大小时,静态成员不算在内) 2. 静态成员变量必须在 类外 定义 ,定义时不添加static关键字(定义要给值,不给值默认是0,最好还是给值) 3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问 4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员 5. 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返值 6.静态变量和全局变量都放在静态区,不是公共代码段

4.静态成员的访问形式

(1)静态成员变量公有时:         ①可以通过对象访问         ②通过整个类域访问 但如果是私有就不能直接访问
//统计A类型对象创建了多少个?构造和拷贝构造各调用了多少次?
class A
{
public:
	A()
	{
		++_count1;
	}

	A(const A& aa)
	{
		++_count2;
	}
//private:
	// 静态成员变量属于整个类,所以类的所有对象
	static int _count1;
	static int _count2; // 声明

	int _a;
};

// 定义
int A::_count1 = 0;
int A::_count2 = 0;

A Func(A a)
{
	A copy(a);
	return copy;
}

int main()
{
	A a1;
	A a2 = Func(a1);

	 //静态成员变量属于整个类,所以类的所有对象
	cout << a1._count1 << endl;
	cout << a1._count2 << endl;

	cout << a2._count1 << endl;
	cout << a2._count2 << endl;

	cout << A::_count1 << endl;
	cout << A::_count2 << endl;
}

 (2)静态成员变量私有时:

为了突破类域,需要在类中设置成员函数去返回私有静态成员变量。

但成员函数调用还需要有对象才能调用,如果没对象呢?——没对象就用静态成员函数

静态成员函数特征:

①静态成员函数没有隐藏的this指针

②不能访问任何非静态成员(因为没有this指针)

class A
{
public:
	A()
	{
		++_count1;
	}

	A(const A& aa)
	{
		++_count2;
	}

	// 成员函数也可以是静态,static成员函数没有this指针
	static int GetCount1()
	{
		//_a = 0;   //不能访问非静态成员,因为没有this指针(这个写法错误)
		
        //	A aa;   //成员函数中再定义对象,用对象访问私有成员变量是可以的(这个写法正确)
		//	aa._a = 0;

		return _count1;
	}

	static int GetCount2()
	{
		return _count2;
	}
private:
	// 静态成员变量属于整个类,所以类的所有对象
	static int _count1;
	static int _count2; // 声明

	int _a;
};

// 定义
int A::_count1 = 0;
int A::_count2 = 0;

A Func(A a)
{
	A copy(a);
	return copy;
}

int main()
{
	A a1;
	A a2 = Func(a1);
	cout << a1.GetCount1() << endl;
	cout << a2.GetCount2() << endl;

	cout << A::GetCount1() << endl;
	cout << A::GetCount2() << endl;
}

五.C++11 的成员初始化新玩法。

1.C++11支持非静态成员变量在声明时进行初始化赋值 ( 这里不是初始化,这里是给声明的成员变 量缺省值 )

有以下三种写法:
class B {
public:
	B(int b = 0)
		:_b(b)
	{}
	int _b;
};
class A {
public:
	void Print()
	{
		cout << a << endl;
		cout << b._b << endl;
		cout << p << endl;
	}
private:
	// 非静态成员变量,可以在成员声明时给缺省值。
	int a = 10;                //1.正常给缺省值的写法
	B b = 20;                  //2.内置类型通过 隐式类型转换 给缺省值的写法
	int* p = (int*)malloc(4);  //3.声明给函数初始化的写法
	static int n;        //静态成员变量不可以给缺省值,原因在下面
};
int A::n = 10;
int main()
{
	A a;
	a.Print();
	return 0;
}

静态成员变量不可以给缺省值原因:

比如:static int n=10; 这样写就是错的 

原因:静态成员变量只有声明在类中,定义在类外,如果你给了缺省值,但是定义没写,静态成员变量也不会在初始化列表初始化;非静态成员变量定义在构造函数的初始化列表中,就算您不写构造函数,也会自动生成默认构造函数,反正 非静态成员变量 肯定会在类中定义,所以他们可以给缺省值。

2.静态成员变量的OJ题:JZ64 求1+2+3+...+n

题目链接:求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com) 

 

答案:用类Sum构建一个变长数组a[n],数组中有n个对象,构造n个对象需要调用n次jis构造函数,再通过 _i 记录构造调用是第几次,_ret把每一次的 _i 加起来,一直加到n,就相当于了1+2+……+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 a[n];
        return Sum::GetRet();
    }
};

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/950368.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号