1,c++标准库
1,c++标准库并不是c++语言的一部分
2,c++标准库是由c++语言编写而成的类库和函数的集合。
3,c++标准库中定义的类和对象都位于std名称空间中。
4,c++标准库涵盖了c库的功能
5,c++标准库定义了多数常用数据结构,字符串,链表队列,栈等。
c++中的输入输出是由cout << 或 >> endl输入输出的,在c语言中<< 或 >> 只能用于左移右移,那么在c++中为什么会变语义了呢?
2,操作符重载:
操作符重载为操作符提供不用的语义,一个相同函数出在不用的上下文的时候,表现出不用的行为。
c++通过operators关键字利用函数拓展操作符。
operator本质是通过函数重载实现操作符重载。
#include <iostream>
using namespace std;
struct Complex
{
int a;
int b;
};
Complex operator+ (const Complex& c1, const Complex& c2)
{
Complex ret;
ret.a = c1.a + c2.a;
ret.b = c1.b + c2.b;
return ret;
}
int main()
{
Complex c1 = {1,2};
Complex c2 = {3,4};
Complex c3 = operator+(c1, c2);
cout<<"c3.a = " << c3.a << "+j" << c3.b<< endl;
return 0;
}
那么用operator关键字扩展的操作符可以用于类吗?
class complex{
private:
int a;
int b;
public:
int c;
}
上面的类如果当我们调用其成员a,b就会出错,因为a,b是类的私有成员,在全局函数operator是不能够访问私有成员的,这个时候我们就需要用到下面的了
3 ,c++中类的友元
1,private声明使得类的成员不能被外界访问,但是通过friend关键字可以例外的开放权限
同样 我们可以将上面的操作符重载函数在类中声明为友元
例如:
class Complex
{
private:
int a;
int b;
public:
int c;
friend Complex operator+ (const Complex& c1, const Complex& c2);
}
这样我们就可以使用了。
操作符重载的本质是通过函数扩展操作符的语义。
操作符重载遵循函数重载的规则。
4,操作符重载下
通过operator关键字能将操作符定义为全局函数。
操作符重载的本质就是函数重载。
问:
类的成员函数是否可以作为操作符重载的函数?
看一个例子:
#include <cstdlib>
#include <iostream>
using namespace std;
class Complex
{
private:
int a;
int b;
public:
Complex(int a = 0, int b = 0)
{
this->a = a;
this->b = b;
}
int getA()
{
return a;
}
int getB()
{
return b;
}
friend Complex operator+ (const Complex& c1, const Complex& c2);
friend ostream& operator<< (ostream& out, const Complex& c);
friend int main(int argc, char *argv[]);
};
Complex operator+ (const Complex& c1, const Complex& c2)
{
Complex ret;
ret.a = c1.a + c2.a;
ret.b = c1.b + c2.b;
return ret;
}
ostream& operator<< (ostream& out, const Complex& c)
{
out<<c.a<<"+"<<c.b<<"i";
return out;
}
int main(int argc, char *argv[])
{
Complex c1(1, 2);
Complex c2(3, 4);
Complex c3 = c1 + c2;
cout<<c1;
operator<<(cout,c1)<<endl;
cout<<c2;//<<endl;
//cout<<c3<<endl;
return 0;
}
用成员函数重载的操作符
1,比全局操作符函数少一个参数,左操作数
2,不需要使用friend关键字
所以说,即便我们用成员函数重载,本质上还是函数,c++中的操作符重载,必然是利用函数实现的,不过我们可以选择有的用成员函数来实现,或者全局函数来实现
那么什么时候用全局函数重载操作符,什么时候使用成员函数来重载操作符呢?
1,当无法修改左操作数的类时,使用全局函数进行重载。
(在这个例子中,我们的operator+重载函数的第一个参数就是我们自定义的Complex类,所以我们可以采用全局函数重载,也可以使用成员函数重载。)我们要想通过成员函数来重载ostream中的<<操作符,必须要修改ostream这个类,而左移操作符<<函数中的第一个参数ostream,是c++标准库中的类ostream,这个类不是我们自定义的,无法修改,这时只能声明一个友元,以全局函数的方式重载。
friend ostream& operator<< (ostream& out, const Complex& c);
2,其他情况可以选择成员函数重载,或者全局函数重载,但是有4个例外。=,[],()和->操作符只能通过成员函数进行重载。
3,=,[],()和->操作符只能通过成员函数进行重载。
赋值,数组运算,函数调用和指针操作符,c++中规定,这四个操作符,能且只能用成员函数进行重载。
重载数组操作符[]
首先有一个疑问,为什么要重载这些操作符?有什么作用?
首先看下面这个例子
//main.c
#include <stdio.h>
#include "Array.h"
int main()
{
Array a1(10);
for(int i=0; i<a1.length(); i++)
{
a1.setData(i, i);
}
for(int i=0; i<a1.length(); i++)
{
printf("Element %d: %d\n", i, a1.getData(i));
}
Array a2 = a1;
for(int i=0; i<a2.length(); i++)
{
printf("Element %d: %d\n", i, a2.getData(i));
}
return 0;
}
从上面的代码中,很难看出Array是一个数组。因为数组都是这样子来使用的,a[]。
这时,我们就可以通过操作符重载,使得Array支持[]操作符。
#ifndef _ARRAY_H_
#define _ARRAY_H_
class Array
{
private:
int mLength;
int* mSpace;
public:
Array(int length);
Array(const Array& obj);
int length();
~Array();
int& operator[](int i);
Array& operator= (const Array& obj);
bool operator== (const Array& obj);
bool operator!= (const Array& obj);
};
#endif
这样我们的array就可以当做数组使用了,如下
#include "Array.h"
int main()
{
Array a1(10);
Array a2(0);
Array a3(0);
if( a1 != a2 )
{
printf("a1 != a2\n");
}
for(int i=0; i<a1.length(); i++)
{
a1[i] = i + 1;
}
for(int i=0; i<a1.length(); i++)
{
printf("Element %d: %d\n", i, a1[i]);
}
a3 = a2 = a1;
if( a1 == a2 )
{
printf("a1 == a2\n");
}
for(int i=0; i<a2.length(); i++)
{
printf("Element %d: %d\n", i, a2[i]);
}
return 0;
}
这样,定义了数组对象a1,然后对a1中的元素进行了赋值。单看对象的操作,跟c语言中的数组一模一样,并且,更加强大的是,我们可以通过成员函数 length来得到数组的长度。而c语言中的是原生态数组是无法得到数组长度的。但是c++中利用操作符重载实现的数组就具有很强的特性。
问:为什么[]重载函数的返回值,是引用类型int&呢?
答:a1[i]相当于一个函数调用:a1.operator[](i),返回的是一个堆空间的元素地址,而这个元素既可以作为左值,又可以作为右值来使用。如果将返回值类型改成int,编译就会出错。之前的课程说过如果要想将函数调用作为左值来使用,返回值类型必须为一个引用类型。否则,只能作为一个右值使用。所以,重载操作符时,返回值类型必须是一个引用类型。
重载赋值操作符=
int main()
{
Array a1(10);
Array a2(0);
for(int i=0; i<a1.length(); i++)
{
a1[i] = i;
}
for(int i=0; i<a1.length(); i++)
{
printf("Element %d: %d\n", i, a1.getData(i));
}
a2 = a1;//将a2赋给a1
for(int i=0; i<a2.length(); i++)
{
printf("Element %d: %d\n", i, a2.getData(i));
}
return 0;
}
当运行的时候会出现同一个堆空间被释放了两次
那么调用拷贝构造函数又如何呢?
int main()
{
Array a1(10);
for(int i=0; i<a1.length(); i++)
{
a1[i] = i;
}
for(int i=0; i<a1.length(); i++)
{
printf("Element %d: %d\n", i, a1.getData(i));
}
Array a2 = a1;
return 0;
}
这个时候拷贝构造函数起到了作用,析构时a1,a2释放的是各自的堆空间,但是a2的值是不确定的,因为并没有对其赋值。
在上述的程序中,自定义了拷贝构造函数,避免了两个对象指向同一个堆空间的情况,但是由于没有进行初始化,所以数组对象a2的值是不确定的(注:在声明数组对象时直接初始化,调用的是拷贝构造函数,声明之后在赋值,调用的是重载操作符函数)。
调用重载操作符函数:
c++编译器默认为每个类都提供了一个赋值操作符的重载,默认赋值操作符重载的实现,是成员变量的值的复制。a1赋值给a2后,a1所指向的mspace,直接赋值给了a2的mspace,即a2也指向了a1的mspace,二者指向了相同的堆空间, a2自己的mspace被泄漏掉了。在析构时,就肯定会出现问题。解决方法是:
重载赋值操作符。方法与自定义拷贝构造函数类似。先释放自己的原有空间,然后自己申请一段新的堆空间,这个堆空间大小与a1相同,之后再进行深度赋值操作。在函数返回时,我们要返回的是Array这个对象,而成员函数本身隐含了一个this指针指向其自身,所以,返回*this即可。