C++:1.流插入与流提取的运算符重载,2.初始化列表,3.explicit关键字,4.静态成员变量与非静态成员变量用法 详解

目录

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

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

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

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

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

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

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

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

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

二.初始化列表

引入:

初始化方式:

 为什么要有初始化列表:

1.定义

2.格式

3.特征

那为什么这三个成员必须在定义时初始化?:

(1)例子1:都初始化

(2)例子2:自定义类型有默认构造函数,则不用在初始化列表初始化

(3)例子3:

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

 (5)对特征4.的解释

三.explicit关键字

1.用途:

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

(1)解释构造函数的隐式类型转换 Date d2 = 2022;  

(2)解释 const Date& d6=2022 必须加const才对

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

(4)类似隐式类型转换的另一种优化

(5)只要支持传单个参数的构造函数,都支持隐式类型转换

四.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 <iostream>
#include <assert.h>
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<<d1
	//int i = 1;
	//double d = 2.2;
	//cout << i << endl;
	//cout << d << endl;
}

int main()
{
	TestDate7();
	return 0;
}

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

如果要把 <<函数重载 写在日期类中,日期类默认有this指针,调用void operator<<(std::ostream& out)时因为是双目运算符,第一个参数默认传给this指针,第二个参数传给形参,所以调用时只能写成d1 << cout; ,这样虽然是对的,但是为了保持可读性,正常的写法应该是 cout<<d1 ,所以必须想办法把两个参数反过来。

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

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

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

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.h: 

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.h:  

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

二.初始化列表

引入:

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

初始化方式:

(1)函数体内初始化,即用默认构造函数初始化
(2)初始化列表初始化

 为什么要有初始化列表:

普通成员变量_year,_month,_day,这些可以在定义时初始化,也可以定义时不初始化后面再赋值修改,但 引用,const,自定义类型(无默认构造函数)的成员变量必须在定义时初始化

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

class Date
{
public:
	 //初始化列表可以认为就是对象的成员变量定义的地方
	Date(int year, int n, int a)
		:_n(n)
		, _ref(value)
        , _aa(a)
	{
		_year = year;
		//_n = n;   这样是错的
		//_ref = ref;
	}

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

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

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

	return 0;
}

1.定义

初始化列表:就是对象的成员变量定义的地方

2.格式

格式: 成员变量() 

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

int value = 10;
class Date
{
public:
		Date(int year, int n, int a)
		:_n(n)
		, _ref(value)
		, _year(year)    
		, _aa(a)
	{
       // _year=year;    //_year也可以在函数体内定义
    }
private:
	int _year; 
	const int _n;
	int& _ref;
	A _aa;
};

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

	return 0;
}

3.特征

1. 每个成员变量在初始化列表中 只能出现一次 ( 初始化只能初始化一次 )
2. 类中包含以下成员,必须放在初始化列表位置进行初始化:(原因是下面红字)
        ①引用成员变量
        ②const成员变量
        ③自定义类型成员(该类没有默认构造函数)如果没有默认构造函数像例子1的成员变量A _aa;类A无默认构造函数,则必须在初始化列表定义_aa(a);如果有默认构造函数,像例子2,就可以不在初始化列表初始化了,因为走读初始化列表时会自动调用_aa的默认构造函数去初始化他自己。

原因:这三个都是必须在定义的时候初始化,而且初始化列表是变量定义的地方,所以必须在初始化列表初始化,其他的变量既可以在初始化列表初始化,也可以在函数体内初始化

那为什么这三个成员必须在定义时初始化?:

①引用成员变量你上来定义个引用,你必须得说清楚这个引用是谁的别名,如果你不在定义时初始化它,那就是没说清楚这个引用是谁的别名,比如例子1中 int& _refvalue的别名,所以把 value 赋给 _ref  ,即:在初始化列表中初始化_ref :_ref(value)

②const成员变量只有一次初始化机会,初始化后不得修改,如果你定义时没有初始化,那你后面也无法修改,那这个const成员变量就没有被初始化。

③自定义类型成员初始化时必须调用构造函数,如果你定义时没有初始化(就是没有在初始化列表初始化 -> 相当于例子1中的_aa(a)不写),正常情况走读初始化列表时会自动调用 自定义类型成员_aa 的默认构造函数去初始化他自己,_aa(a)不写=没传参,没传参也可以正常初始化,如果没有默认构造函数,就只能调用普通带参构造函数(例子1中的A(int x) 就是普通带参构造函数),你_aa(a)都没写,参数没传,根本调用不动普通带参构造函数。即:调用普通带参构造函数需要显示的写参数,这个问题详情请见 C++入门:构造函数,析构函数,拷贝构造函数,运算符重载详解_beyond.myself的博客-CSDN博客  大标题一 —>小标题2. —>(7)

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

(1)例子1:都初始化

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

int value = 10;
class A
{
public:
	A(int x)    //无默认构造函数,这个是普通带参构造函数
		:_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:自定义类型有默认构造函数,则不用在初始化列表初始化

因为走初始化列表时会自动调用_aa的默认构造函数去初始化他自己。

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;
}

(3)例子3:

有一个类A,其数据成员如下: 则构造函数中,成员变量一定要通过初始化列表来初始化的是哪些?

class A {
...
private:
   int a;
public:
   const int b;
   float* &c;
   static const char* d;
   static double* e;
};

a是不同数据成员,可以通过构造函数进行赋值

b,c : const常量以及引用只能通过初始化列表初始化

d,e 是静态成员,只能在类外初始化

结论:所以答案是b,c 只能通过初始化列表初始化。

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

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(const MyQueue& q)    
	{}*/
//拷贝构造也是构造函数,你写了拷贝构造就不会生成默认构造函数,所以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为随机值。(缺省值还是属于在初始化列表初始化的)

 (5)对特征4.的解释

4. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。先按类声明顺序初始化完初始化列表里的变量,再初始化函数内的成员变量

比如下面这个:因为类A有默认构造函数,则可以不用在初始化列表初始化成员变量_aa,因为走读初始化列表时会自动调用_aa的默认构造函数去初始化他自己。

分析初始化顺序:

看类中声明顺序,_year虽然排第一,但是他是在函数体内定义,所以肯定在走完初始化列表再初始化。所以初始化列表中 ①先初始化_n=1。 ②再初始化_aa,去调用他的类的默认构造函数去初始化_aa=0。 ③再初始化_ref=10。④最后结束初始化列表,走到函数体内初始化,初始化_year=2022。⑤定义了一个变量aa=10(因为实参a是10),又把_aa赋成aa,也就是_aa=10。_aa前面走的是默认构造函数,函数体内走的是赋值初始化。

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;
		A aa(a);
		_aa = aa;
	}

private:
	int _year;
	const int _n;
	A _aa;
	int& _ref;
};

int main()
{
	Date d1(2022, 1, 10);

	return 0;
}

再看一个选择题:

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;
	}

	/*explicit 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.调用拷贝构造函数
    const Date& d6=2022 
	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.h>

//库中的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;
}

(4)类似隐式类型转换的另一种优化

匿名对象只存在于构造该对象的那行代码,离开构造匿名对象的那行代码后立即调用析构函数。

下面分别调用了多少次构造函数,拷贝构造函数,析构函数?

答:调用1次构造,调用3次拷贝构造函数,调用4次析构函数

class Weight
{
public:
	Weight()
	{
		cout << "Weight()" << endl;
	}

	Weight(const Weight& w)
	{
		cout << "Weight(const Weight& w)" << endl;
	}

	Weight& operator=(const Weight& w)
	{
		cout << "Weight& operator=(const Weight& w)" << endl;
		return *this;
	}

	~Weight()
	{
		cout << "~Weight()" << endl;
	}

};
Weight f(Weight u)
{
	Weight v(u);
	Weight w = v;

	return w;
}
int main()
{
	//Weight x;
	//f(x);
	f(Weight());   //  Weight(); 匿名对象,声明周期只在这一行

	return 0;
}

f(Weight());  中实参是匿名对象,本应该是先构造匿名对象,随后传值传参时把实参拷贝构造给形参u,但由于编译器优化,把先构造后立刻拷贝构造优化为直接构造,实参匿名对象和形参u合二为一,直接构造一个对象,所以相对于先构造一个对象x,再用x传参,这里是少调用了一次拷贝构造和一次析构函数。(x传参以及更多优化题目详解详情见http://t.csdn.cn/k9Psm 大标题三,小标题4(2),(3),(5))

(5)只要支持传单个参数的构造函数,都支持隐式类型转换

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

int main()
{
	Date d1=2022;
	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. 类静态成员变量/函数 都可以用 类名::静态成员 或者 对象.静态成员 来访问。例如:
静态成员变量 a1._count1 , A::_count1都可以; 静态成员函数 a1.GetCount1() , A::GetCount1()都可以
注意:这里的 a1._count1 不会传递指针,不会传a1地址,因为没有this指针,仅仅为了指定类域,a1._count1 和 A::_count1 访问的是同一个变量。
4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
5. 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返值
6.静态变量和全局变量都放在静态区,不是公共代码段

4.静态成员的访问形式

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

        ①可以通过对象访问 a1._count1 或 a2._count1 访问的都是一个
        ②通过整个类域访问 A._count1
但如果是私有就不能直接访问
//统计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;
}

构造函数调用了1次(看count1);拷贝构造调用了3次(看count2);

 (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; 这里不会传递指针,不会传a1地址,因为没有this指针,
仅仅为了指定类域

	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) 

(1)答案:用类Sum构建一个变长数组a[n],数组中有n个对象,构造n个对象需要调用n次构造函数,再通过静态变量 _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();
    }
};

(2)利用内部类的友元关系

class Solution {
private:    //把sum设为私有,只有自己可以使用,防止别人使用
    class Sum   //Sum是Solution的内部类,Sum是Solution的友元
    {
    public:
        Sum()
        {
            _ret+=_i;
            ++_i;
        }
    };
public:
    int Sum_Solution(int n) {
        Sum a[n];
        return _ret;
    }
private:  //把 _i 和 _ret 由Sum变成Solution的成员变量,这样Sum和Solution都可以访问
    static int _i;
    static int _ret;
};
int Solution::_i=1;
int Solution::_ret=0;

  • 21
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 15
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值