C++学习笔记(模板)

观看慕课网《C++远征》系列课程的笔记,链接https://www.imooc.com/u/1349694/courses?sort=publish

友元函数和友元类

友元全局函数

1、定义在类外的友元函数,能通过该函数访问到对象中私有的数据成员。
2、声明时放在类里面,要加friend,定义时可以放在类外面,可以不加friend

class Coordinate{
	friend void printXY(Coordinate &c)//要求传入当前这个类的对象实例或指针或引用
	{
		cout<<c.m_iX<<","<<c.m_iY<<endl;
	}
public:
	Coordinate(int x,int y);
private:
	int m_iX;
	int m_iY;
};
int main(void){
	Coordinate coor(3,5);
	printXY(coor);
	return 0;
}

考虑以下的变种情形:
1、把friend去掉,此时printXY变成一个私有(默认)成员函数,外部不可访问,编译不能成功
2、把friend去掉,并且将printXY放到public底下,此时它已经是这个类的成员函数了,可以用它去访问类的私有变量
3、把friend去掉,并且将printXY放到类的外部进行定义,此时不能编译,因为类的私有成员不能通过外部的任何函数进行访问。这也是friend的意义。上面的printXY虽然放在类里面定义但是它不属于类的函数, 它只是类的友元函数,而且是全局的。
4、只要加了friend,它不受public、private、protected限制,放在哪都可以,放在三者之外也可以。推荐是放在public上方,因为最显眼。

友元成员函数

1、类A为类B的成员函数赋予权限,允许类B的对象通过成员函数访问类A的私有数据成员

class Coordinate{
	friend void Circle::printXY(Coordinate &c);//在类A中做声明
	{
		cout<<c.m_iX<<","<<c.m_iY<<endl;	//赋予了Circle类的成员函数权限,允许它访问自己的私有变量
	}
public:
	Coordinate(int x,int y):m_iX(x),m_iY(y){ }
private:
	int m_iX;
	int m_iY;
};
class Circle{
public:
	friend void Circle::printXY(Coordinate &c)//在类B中做定义
	{
		cout<<c.m_iX<<","<<c.m_iY<<endl; //赋予了Circle类的成员函数权限,允许它访问Coordinate对象的私有变量
	}
private:
	Coordinate coor; //定义一个私有的类A对象,并不是必要的,与这里要讲的友元没有关系
}
int main(void){
	Coordinate coor(3,5);
	Circle circle;
	circle.printXY(coor);
	return 0;
}

友元类

class Circle; //需要先声明,告诉编译器有这个类
class Coordinate{
	friend Circle;	//声明Circle是自己的友元类,则允许Cirle类对象访问自己的私有成员
public:
	Coordinate(int x,int y):m_iX(x),m_iY(y){ }
private:
	int m_iX;
	int m_iY;
};
class Circle{
public:
	Circle(int x,int y):m_coor(x,y){ }
	void printXY(){
		cout<<m_coor.m_iX<<","<<m_coor.m_iY<<endl;
	}
private:
	Coordinate m_coor;
};
int main(){
	Circle circle(3,4);
	circle.printXY();
}

上方代码可以运行通过,但是如果把Coordinat类中的friend Circle;语句注释掉时,则会报错,错误信息是m_iX是私有的。第一句声明也是必须加上的,把Circle先定义了也不行,因为Circle类中有一个Coordinate成员,这就要求在前面要加上一句class Coordinate; 这两个类有依赖关系,不论谁先谁后定义都躲不开先声明对方的存在。

友元的注意事项

1、友元关系不可传递
2、友元关系具有单向性,A是B的友元,B不一定是A的友元。“A是B的友元”是在A里面进行声明的,宣称别人是自己的朋友即将自己暴露给对方,但对方并不会暴露给自己。
3、友元声明的形式及数量不受限制
4、友元是封装的补充,它不是一个很好的语法,它会破坏封装性,如果前期类设计合理,是不需要用到友元的。

static

静态数据成员

1、静态数据成员不依赖于对象,不需要实例化就已存在,该类实例化出来的所有对象都共享同一个静态数据成员,只有一份
2、它的初始化有自己的方式——变量类型 类名::变量名=初始值
3、访问方式可以既可以通过对象也可以直接通过类
4、用sizeof计算时是不包括静态数据成员的

class Tank{
public:
    Tank(string code):m_strCode(code){
        s_iCount++;		//每实例化一个Tank类,就将实例数量+1
        cout<<"Tank()"<<endl;
    }
    ~Tank(){
        s_iCount--;
        cout<<"~Tank()"<<endl;
    }
    void fire(){
        cout<<"Tank-fire"<<endl;
    }
    static int getCount(){ //静态成员函数
        return s_iCount;
    }
private:
    string m_strCode;
    static int s_iCount;	//静态数据成员,用来统计有多少个Tank类实例
};

int Tank::s_iCount=0;  //初始化Tank类的静态数据成员

int main(){
	//通过类访问静态数据成员,记得是用::而不是用点
    cout<<Tank::getCount()<<endl; //通过静态成员函数,输出0,因为还没有任何实例化的对象
    cout<<Tank::s_iCount<<endl;	//如果s_iCount是public的话则没有错
    //通过对象访问静态数据成员
    Tank tank("hello");
    cout<<tank.getCount()<<endl; //通过静态成员函数,输出1,因为已经实例化了一个对象
    cout<<tank.s_iCount<<endl;	//如果s_iCount是public的话则没有错

}

运算符重载

运算符的重载本质是函数重载

一元运算符重载(单目)

只操作一个对象的运算符叫做一元运算符

负号(-)的重载
成员函数重载

成员函数重载和其他的成员函数一样,第一个传入参数默认是this指针

class Coordinate{
public:
	Coordinate(int x,int y):m_iX(x),m_iY(y);
	Coordinate& operator-(){
		m_iX = -m_iX;
		m_iY = -m_iY;
		return *this;
	}
private:
	int m_iX;
	int m_iY;

};
int main(){
	Coordinate coor(3,4);
	-coor; //等价于coor.operator-();
}
友元函数重载
class Coordinate{
public:
	friend Coordinate& operator-(Coordinate &coor){
		coor.m_iX = -coor.m_iX;
		coor.m_iY = -coor.m_iY;
		return *this
	}
	Coordinate(int x,int y):m_iX(x),m_iY(y);
private:
	int m_iX;
	int m_iY;

};
int main(){
	Coordinate coor(3,4);
	-coor; //等价于operator-(coor);与成员函数重载有区别
}
自加(++)的重载

++前置

class Coordinate{
public:
	Coordinate(int x,int y):m_iX(x),m_iY(y);
	Coordinate& operator++(){
		m_iX++;
		m_iY++;
		return *this
	}
private:
	int m_iX;
	int m_iY;

};
int main(){
	Coordinate coor(3,4);
	++coor; //coor等价于operator++();与成员函数重载有区别
}

++后置

class Coordinate{
public:
	Coordinate(int x,int y):m_iX(x),m_iY(y);
	Coordinate operator++(int){	//返回值时实例对象,括号内(int),告诉编译器这是后置重载
		Coordinate old(*this); //拷贝一个副本放在old
		m_iX++;
		m_iY++;
		return old;	//后置++是先运算后加一,所以还是返回没有+1时的那份
	}
private:
	int m_iX;
	int m_iY;

};
int main(){
	Coordinate coor(3,4);
	++coor; //coor等价于operator++();与成员函数重载有区别
}

二元运算符重载(双目)

需要操作两个对象的运算符叫做二元运算符

+号运算符
成员函数重载
class Coordinate{
public:
	Coordinate(int x,int y):m_iX(x),m_iY(y);
	Coordinate operator+(const Coordinate &coor){
		Coordinate temp;
		temp.m_iX = this->m_iX + coor.m_iX;
		temp.m_iY = this->m_iY + coor.m_iY;
		return temp;
	}
private:
	int m_iX;
	int m_iY;

};
int main(){
	Coordinate coor1(3,4);
	Coordinate coor2(4,7);
	Coordinate coor3(0,0);
	coor3 = coor1+coor2 //coor1.operator+(coor2);
	return 0;
}
友元函数重载
class Coordinate{
public:
	Coordinate(int x,int y):m_iX(x),m_iY(y);
	friend Coordinate operator+(const Coordinate &c1, const Coordinate &c2){
		Coordinate temp;
		temp.m_iX = c1.m_iX + c2.m_iX;
		temp.m_iY = c1.m_iY + c2.m_iY;
		return temp;
	}
private:
	int m_iX;
	int m_iY;

};
int main(){
	Coordinate coor1(3,4);
	Coordinate coor2(4,7);
	Coordinate coor3(0,0);
	coor3 = coor1+coor2 //operator+(coor1,coor2);
	return 0;
}
<<输出运算符

1、cout本质上就是一个ostream对象
2、输出运算符不可以采用成员函数重载,成员函数的第一个传入参数默认是this指针,但是<<的第一个传入参数必须是ostream引用

class Coordinate{
public:
	Coordinate(int x,int y):m_iX(x),m_iY(y);
	friend ostream operator<<(ostream &out, const Coordinate &coor){ //友元函数需要传入一个对象,但不一定要写在参数列表中的第一位
		out<<coor.m_iX<<","<<coor.m_iY;
		return out;
	}
private:
	int m_iX;
	int m_iY;

};
int main(){
	Coordinate coor1(3,4);
	cout<<coor; //operator<<(cout,coor); //cout实际上是一个ostream类型的对象
	return 0;
}
[ ]索引运算符

1、[ ]索引运算符

class Coordinate{
public:
	Coordinate(int x,int y):m_iX(x),m_iY(y);
	int operator[](int index){
		if(index==0)
			return m_iX;
		else if(index==1)
			return m_iY;
	}
private:
	int m_iX;
	int m_iY;
};
int main(){
	Coordinate coor(3,5);
	cout<<coor[0];	//coor.operator[](0);
	cour<<coor[1];  //coor.operator[](1);
}

关于为什么<<不能用成员函数重载,[]为什么不能用友元函数重载
还是理解不了,先附上一个链接:
https://www.cnblogs.com/Tang-tangt/p/9678276.html

函数模板和类模板

函数模板

1、函数模板把类型当做参数,这种参数也叫作模板参数,参数至少有一个。模板参数除了是可以是类型还可以是变量,如下方第6点
2、三个关键字:template,typename,class,后两个的作用是相同
3、函数模板和模板函数,模板函数是在使用函数模板时实例化出来,它才是真正写入到代码区的函数,而函数模板是不会写入到代码区的
4、通过class

//函数模板
template <class T>
T max(T a, T b){	
	return (a>b)?a:b;
}
//模板函数,使用函数模板时才会实例出一个模板函数,
int main(){
	char cval = max<char>("A","B");//指定函数模板的参数为为char
	int value = max(100,99) //也可以不指定,由编译器自己判断类型是什么
}

5、typename与class没有区别,也可以混用
6、变量也可作为模板参数,同样只有实例化时才会真正产生变量,如下方的size。

template <int size>
void display(){
	cout<<size<<endl;
}

7、多模板参数

template <typename T, typename C>
void display(T a, T b){
	cout<<a<<" "<< b<<endl;
}
int main(){
	display<int,char>(10,"10");
}

8、函数模板与重载

//函数模板并没有重载这个概念,因为它们实际上没有产生代码,只有实例化出模板函数时才会有重载
template <typename T>
void display(T a);

template <typename T>
void display(T a, T b);

template <typename T, int size>
void display(T a);

//三个模板函数形成了重载
display<int>(10)
display<int>(10,20)
display<int,5>(10)

类模板

1、类模板中的成员函数如果在外部定义需要注明模板参数
2、类模板也是不会产生实际的内存的,只有在实例化时才会产生

template<class T>
class MyArray{
public:
	void display();
	//在类内定义成员函数与之前相同
	void sort(){  }
private:
	T* m_pArr;
}
//如果在类外定义成员函数,那么要在函数上面先写一句和类上方有一样的参数声明,注意是每一个上方都要写,不是只在文件顶部写一次
//并且类名后方也要注明参数
template<class T>
void MyArray<T>::display(){
	...
}
int main(){
	//在实例化类模板时传入模板参数
	MyArray<int> arr;
}

3、类模板的参数同样可以有变量

template<typename T,int kSize>
class Container{
public:
	void display();
private:
	T m_obj;
};
template<typename T,int kSize>
void Container<T,kSize>::display(){
	for(int i=0;i<kSize;++i)
		cout<<m_obj<<endl;
		
}
int main(){
	Container<int,10>ct1;
	ct1,display();
	
}

模板不能分离编译!!即声明和定义不能分别放在.h文件和.cpp文件中,需要写在同一份文件中

标准模板库(STL)

vector向量

1、本质是对数组的封装,实现了一个动态数组
2、读取数据的时间复杂度为O(1),但是插入平均需要O(n)

初始化方式
vector<int> ivec1;//默认为空
vector<int> ivec2(ivec1); //用一个向量初始化另一个向量
vector<int> ivec3(n,i); //包含n个值为i的元素
vector<int> ivec3(n); //包含值初始化元素的n的副本
int a[5]={0,1,2,3,4};
vector<int> ivec4(a,a+5);//用数组初始化向量,上界不包括在内
vector<int> ivce5(ivec4.begin()+1,ivec4.end()-2); //用向量中的某一段初始化向量,同样不包括上界
方法

empty()
begin()
end()
clear()
front() //第一个数据
back() //最后一个数据
size()
push_back()
pop_back()

迭代器

迭代器是标准模板库中类似指针的存在,迭代器要指明是属于数据类型的迭代器

vector<int> vec;
vec.push_back("hello");
vector<int>::iterator citer = vec.begin()
for(;citer!=vec.end();++citer){
	cout<<*citer<<endl;
}

链表list

1、插入数据只需要O(1),读取需要O(n)
2、list不能用[ ]取值,只能通过迭代器list<类型>::iterator it

push_back()
size()

映射map

map也有迭代器

insert
count
find
begin
end
first
second

map<int,string> m;
pair<int,string> p1(10,"shanghai");
pair<int,string> p2(20,"beijing");
m.insert(p1);
m.insert(p2);
cout<<m[10]<<endl;
cout<<m[20]<<endl;

map<int,string>::iterator it = m.begin();
for(;it!=m.end();++i){
	cout<<*it<<endl;	//这样是会报错的,因为*it既返回了key又返回了value
}
for(;it!=m.end();++i)
	cout<<"Key:"<<it->first<<", Value: "<<it->second<<endl;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值