C++技术点积累(3)——对象初始化列表、运算符重载

来源:互联网 发布:淘宝图片空间怎么导出 编辑:程序博客网 时间:2024/05/25 20:00

C++技术累积:

1、构造函数的对象初始化列表——初始化列表先于  构造函数的函数体  执行

初始化列表的原因

        1)、必须这样做:组合类——即我们有一个类成员(A类),它本身是一个类或者是一个结构,而且这个成员 它只有一个带参数  的构造函数,而没有默认构造函数,这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数,如果没有初始化列表,就无法初始化A类的对象(成员),也就无法确定该类本身的内存空间大小,那么他将无法完成第一步,就会报错。
       2)、类成员中若有const修饰,必须在对象初始化的时候,给const int m 赋值
             当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化,因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的。注意概念:初始化:被初始化的对象正在创建;赋值:被赋值的对象已经存在。
       3)、或者继承的情况下,当父类的构造函数有参数时,需要在子类的初始化列表中显示调用——见C++技术点积累(4)的第一段代码。


先看结论,再看例子:
    1)、构造函数的初始化列表  解决: 在B类中 组合了一个 A类对象 (A类设计了构造函数)
         //根据构造函数的调用规则 设计A的构造函数, 必须要用;但在B中没有机会初始化A
         //新的语法——Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3)
    2)、先执行 被组合对象(A)的构造函数 
        //如果组合对象有多个,按照定义顺序, 而不是按照初始化列表的顺序
        //析构函数 : 和构造函数的调用顺序相反
    3)、被组合对象的构造顺序 与定义顺序有关系 ,与初始化列表的顺序没有关系.
    4)、初始化列表 用来 给const 属性赋值 

#include <iostream>using namespace std;class A{public:A(int _a){a = _a;cout << "构造函数" << "a" << a << endl;}~A(){cout << "析构函数" << "a" << a << endl;}protected:private:int a;};//1、构造函数的初始化列表  解决: 在B类中 组合了一个 A类对象 (A类设计了构造函数)//根据构造函数的调用规则 设计A的构造函数, 必须要用;但在B中没有机会初始化A//新的语法——Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3)class B{public:B(int _b1, int _b2) : a1(1), a2(2), c(0){}B(int _b1, int _b2, int m, int n) : a1(m), a2(n), c(0){b1 = _b1;b2 = _b2;cout << "B的构造函数" << endl;}~B(){cout << "B的析构函数" << endl;}protected:private:int b1;int b2;A a2;A a1;const int c;};//2 先执行 被组合对象(A类)的构造函数 //如果组合对象有多个,按照定义顺序, 而不是按照初始化列表的顺序//析构函数 : 和构造函数的调用顺序相反//3 被组合对象的构造顺序 与定义顺序有关系 ,与初始化列表的顺序没有关系.//4 初始化列表 用来 给const 属性赋值 void obj10play(){//A a1(10);//B ojbB(1, 2);//1参数传递 B ojbB2(1, 2, 3, 4);//2 调用顺序return;}void main(){obj10play();system("pause");}
注意:拷贝构造函数同样需要使用初始化列表。
上述程序执行效果:


补充:匿名对象的生命周期

int run3(){printf("run3 start..\n");//ABCD(400, 500, 600); //临时对象的生命周期——生命周期只存在这一行,这一行执行完构造函数以后,紧接着就会执行析构函数ABCD abcd = ABCD(100, 200, 300);  //扶正!有名了!——abcd//在构造函数里面调用另外一个构造函数,会有什么结果?printf("run3 end\n");return 0;}

2、1)进行一元运算符重载比如前置++、后置++、前置--、后置--时,

      因为函数返回值也不能作为区别重载函数的条件(通常都是作为类的成员函数),所以这个时候使用——占位符!C++中通过一个占位参数来区分前置运算和后置运算。这个形参的唯一作用就是区分前置后置版本的函数,而不是真的要在实现后置版本时参与运算。

//前置--//前置运算符返回递增或递减后对象的引用Complex& operator--()       //函数返回值不能作为区别重载函数的条件{this->a--;this->b--;return *this;}//后置--//后置运算符返回对象的原值(递增递减以前的值),返回的形式是一个值而非引用Complex operator--(int)     //占位符——以区别前置--、后置--{Complex tmp = *this;this->a--;this->b--;return tmp;}
可以显式调用一个重载的运算符,其效果与表达式中以运算符号形式使用它完全一样。
Complex C;
C.operator++(0);  //调用后置版本,必须为其传入一个值
C.operator--();

2)注意:输入输出(>>、<<)流的重载 只能使用  友元全局函数来实现——

     A.为什么要重载<<和>>?
          istream 和 ostream 是 C++ 的预定义流类 ,cin 是 istream 的对象,cout 是 ostream 的对象;运算符 << 由ostream 重载为插入操作,用于输出基本类型数据,运算符 >> 由 istream 重载为提取操作,用于输入基本类型数据;用友员函数重载 << 和 >> ,输出和输入用户自定义的数据类型,比如一个类对象。也就是说C++编译器的>>、<<支持输入输出基本数据类型,现在我们重载>>、<<就是要让编译器支持输入输出我们自定义的数据类型,并且我们还可以“个性化”我们自己的输入输出。

     B.如果要使用成员函数cout.operator<<(obj),我们就需要去修改添加cout(ostream)类的成员函数,在cout类中添加成员函数.operator<<(Object  obj),而我们不可能获取ostream类的源码。也就是说如果我们采用成员函数去重载<< 和 >>,那么我们就需要在cout中添加该操作符重载的成员函数,而不是我们的类。

        假如obj是一个对象实例,

            【采用友元全局函数:】     cout << obj;    ===》  operator(左操作数cout,右操作数obj)          ——可行;

            【    采用成员函数   :】      cout << obj;    ===》  cout.operator<<(obj)     ——修改cout类源码,不可行;

      C.为什么返回引用?

          返回一个引用,这样函数返回值就可以当左值,支持连续输出,链式编程,流式概念。//cout << str1 << str2 << str3 << endl;


3)重载=赋值运算符
赋值运算符重载用于对象数据的复制 ,operator= 必须重载为成员函数( obj1 = obj2 ——》obj1.operator=(obj2) ),1、先释放旧的内存;2 返回一个引用 ;3 =操作符 从右向左

#define  _CRT_SECURE_NO_WARNINGS #include <iostream>using namespace std;class  Name{public:Name(const char *myp){m_len = strlen(myp);m_p =(char *) malloc(m_len + 1); strcpy(m_p, myp);}//Name obj2 = obj1;初始化//C++编译器提供的 默认的copy构造函数  浅拷贝//解决方案: 手工的编写拷贝构造函数,使用深copyName(const Name& obj1){m_len = obj1.m_len;m_p = (char *)malloc(m_len + 1);strcpy(m_p, obj1.m_p);}//obj3 = obj1; 赋值// C++编译器提供的 =(等号)操作,也属 浅拷贝//obj3.operator=(obj1)Name& operator=(Name &obj1){//重载 等号操作运算符 步骤//1 先释放旧的内存if (this->m_p != NULL){delete[] m_p;m_len = 0;}//2 根据obj1分配内存大小this->m_len = obj1.m_len;this->m_p = new char [m_len+1];//3 把obj1赋值strcpy(m_p, obj1.m_p);return *this;}~Name(){if (m_p != NULL){free(m_p);m_p = NULL;m_len = 0;}}private:char *m_p ;//类里面有指针,很可能会出现浅拷贝深拷贝问题int m_len; };//对象析构的时候 出现coredumpvoid objplaymain(){Name obj1("abcdefg");Name obj2 = obj1;  //C++编译器提供的 默认的copy构造函数  浅拷贝Name obj3("obj3");obj3 = obj1;  // C++编译器提供的 等号操作 也属 浅拷贝//obj3.operator=(obj1)//operato=(Name &obj1)obj1 = obj2 = obj3;//obj2.operator=(obj3);//obj1 = void;     //重载 等号操作运算符时,返回void,不支持这种链式编程//重载 等号操作运算符时,返回引用才可以支持这种链式编程}void main(){objplaymain();}


4)总结:
操作符重载 是C++的强大特性之一
操作符重载 的本质是 通过函数 扩展操作符的语义
operator关键字 是操作符重载的关键
friend关键字 可以对函数或类 开发访问权限
操作符重载 遵循函数重载 的规则
操作符重载 可以直接使用类的成员函数实现
=,[],()——函数调用符和 ->操作符 只能通过 成员函数 进行重载
++操作符 通过一个 int参数 进行前置与后置的重载(区别前后置)


           C++中 不要重载 && 和 || 操作符——原因:1)&&和||是C++中非常特殊的操作符 ;2)&&和||内置实现了短路规则 ;3)操作符重载是靠函数重载来完成的 ;4)操作数作为函数参数传递 ;5)C++的函数参数都会被求值无法实现短路规则

//C++中 不要重载 && 和 || 操作符#include <cstdlib>#include <iostream>using namespace std;class Test{int i;public:Test(int i){this->i = i;}Test operator+ (const Test& obj){Test ret(0);cout << "执行+号重载函数" << endl;ret.i = i + obj.i;return ret;}bool operator&& (const Test& obj){cout << "执行&&重载函数" << endl;return i && obj.i;}};// && 从左向右void main(){int a1 = 0;int a2 = 1;cout << "注意:&&操作符的结合顺序是从左向右" << endl;if (a1 && (a1 + a2)){cout << "有一个是假,则不在执行下一个表达式的计算" << endl;}Test t1 = 0;Test t2 = 1;//无法实现 短路规则//if( t1 && (t1 + t2)  )//t1  &&  t1.operator+(t2)//t1.operator&&(  t1.operator+(t2) )   //1 &&、||,重载他们,不会产生短路效果if ((t1 + t2) && t1){//无法实现 短路规则//t1.operator+(t2) && t1;//(t1.operator+(t2)).operator&&(t1);cout << "两个函数都被执行了,而且是先执行了+" << endl;}//2 && 运算符的结合性//两个逻辑与运算符  在一块的时候, 才去谈 运算符的结合性//if(  (t1 + t2) && t1 && t2 )//从左到右  (t1 + t2) && t1 ----> 运算结果 && t2){//t1.operator+(t2) && t1;//(t1.operator+(t2)).operator&&(t1);cout << "两个函数都被执行了,而且是先执行了+" << endl;}system("pause");return;}



以下示例程序作了详细解释,只为练手,可略过!

运算符重载案例示例(1)——Array类:
MyArray.h

#pragma  once#include <iostream>using namespace std;class Array{public:Array(int length);Array(const Array& obj);~Array();public:void setData(int index, int valude);int getData(int index);int length();private:int m_length;int *m_space;public://函数返回值要能当左值,所以需要返回一个引用//应该返回一个引用(元素本身) 而不是一个值int& operator[](int i);//重载=Array& operator=(Array &a1);//重载 ==bool operator==(Array &a1);//重载 !=bool operator!=(Array &a1);};
MyArray.cpp
#include <iostream>#include "myarray.h"Array::Array(int length){if (length < 0){length = 0; //}m_length = length;m_space = new int[m_length];}//Array a2 = a1;Array::Array(const Array& obj){this->m_length = obj.m_length;this->m_space = new int[this->m_length]; //分配内存空间for (int i=0; i<m_length; i++)   //数组元素复制{this->m_space[i] = obj.m_space[i];}}Array::~Array(){if (m_space != NULL){delete[] m_space;m_space = NULL;m_length = -1;}}//a1.setData(i, i);void Array::setData(int index, int valude){m_space[index] = valude;}int Array::getData(int index){return m_space[index];}int Array::length(){return m_length;}//重载[]数组下标运算符——m_space标识了一段内存空间,m_space[i]是有效的,//所以我们借助m_space[]来 重载 自定义的数组类MyArray的[],使其MyArray类的对象ma可以直接使用[]操作符//a1[i] = i;函数返回值当左值,需要返回一个引用//应该返回一个引用(元素本身) 而不是一个值int& Array::operator[](int i)  {return m_space[i];}//重载=赋值运算符//a3 = a1;Array& Array::operator=(Array &a1){//1 释放原来的内存空间if (this->m_space != NULL){delete [] m_space;m_length = 0;}//2 根据a1大小 分配内存 m_length = a1.m_length;m_space = new int[m_length];//3 copy数据for (int i=0; i<m_length; i++){//m_space[i] = a1.m_space[i];m_space[i] = a1[i];  //[]运算符已经重载,所以可以直接使用a1[i]}return *this;}//重载==运算符//if (a3 == a1)bool Array::operator==(Array &a1){if (this->m_length != a1.m_length){return false;}for (int i=0; i<m_length; i++){if (this->m_space[i] != a1[i]){return false;}}return true;}//重载!=运算符bool Array::operator!=(Array &a1){/*if (*this == a1){return true;}else{return false;}*/return !(*this == a1);//已经重载==运算符}
MyArray_Test.cpp
#include <iostream>#include "myarray.h"using namespace std;//类的框架设计完毕//类的测试案例//重载[]//void operator[](int i)//int operator[](int i);//int& operator[](int i);void main(){Array  a1(10);for (int i=0; i<a1.length(); i++){a1.setData(i, i); a1[i] = i;//函数返回值当左值,需要返回一个引用//a1.operator [i]}cout<<"\n打印数组a1: ";for (int i=0; i<a1.length(); i++){//cout<<a1.getData(i)<<" ";cout<<a1[i]<<endl;}cout<<endl;Array a2 = a1;cout<<"\n打印数组a2: ";for (int i=0; i<a2.length(); i++){cout<<a2.getData(i)<<" ";}cout<<endl;Array a3(5);{a3 = a1;a3 = a2 = a1;//a3.operator=(a1)//Array& operator=(Array &a1)cout<<"\n打印数组a3: ";for (int i=0; i<a3.length(); i++){cout<<a3[i]<<" ";}}cout<<endl;if (a3 == a1){printf("相等\n");}else{printf("不相等\n");}cout<<endl;if (a3 != a1){printf("不相等\n");}else{printf("相等\n");}cout<<"hello..."<<endl;system("pause");return ;}


运算符重载案例示例(2)——MyString类:
MyString.h

#pragma once#include <iostream>using namespace std;//c中没有字符串,实现一个 字符串类(c风格的字符串)//空串 ""class  MyString{//重载 << 和 >>friend ostream& operator<<(ostream &out, MyString &s);friend istream& operator>>(istream &in, MyString &s);public:MyString(int len = 0); //MyString s1;MyString(const char *p); //MyString s2("s2");//MyString s4 = "s4444444444";MyString(const MyString& s); //MyString s3 = s2;~MyString();public: //重载 = 和 []MyString& operator=(const char *p); //s4 = "s2222";MyString& operator=(const MyString &s); //s4 = s2;char& operator[] (int index);   //s4[1] = '4';public: //重载 == 和 !== bool operator==(const char *p) const;bool operator==(const MyString& s) const;bool operator!=(const char *p) const;bool operator!=(const MyString& s) const;public: //重载 < 和 >int operator<(const char *p);int operator>(const char *p);int operator<(const MyString& s);int operator>(const MyString& s);private:intm_len;char*m_p;};
MyString.cpp
#define _CRT_SECURE_NO_WARNINGS#include "MyString.h"ostream& operator<<(ostream &out, MyString &s)//返回一个引用,这样函数返回值就可以当左值,支持连续输出,链式编程,流式概念{out<<s.m_p;return out;}istream& operator>>(istream &in, MyString &s){in>>s.m_p;    //?????????????return in;}MyString::MyString(int len){if (len == 0){m_len = 0;m_p = new char[m_len + 1];strcpy(m_p, "");}else{m_len = len;m_p = new char[m_len + 1];memset(m_p, 0, m_len);}}MyString::MyString(const char *p){if (p == NULL){m_len = 0;m_p = new char[m_len + 1];strcpy(m_p, "");}else{m_len = strlen(p);m_p = new char[m_len + 1];strcpy(m_p, p);}}//拷贝构造函数//MyString s3 = s2;MyString::MyString(const MyString& s){m_len = s.m_len;m_p = new char[m_len + 1];strcpy(m_p, s.m_p);}MyString::~MyString(){if (m_p != NULL){delete [] m_p;m_p = NULL;m_len = 0;}}// s4 = "s2222";MyString& MyString::operator=(const char *p){//1 旧内存释放掉if (m_p != NULL){delete [] m_p;m_len = 0;}//2 根据p分配内存if (p == NULL){m_len = 0;m_p = new char[m_len + 1];strcpy(m_p, "");}else{m_len = strlen(p);m_p = new char[m_len + 1];strcpy(m_p, p);}return *this;}// s4 = s2;MyString& MyString::operator=(const MyString &s){//1 旧内存释放掉if (m_p != NULL){delete [] m_p;m_len = 0;}//2 根据s分配内存m_len = s.m_len;m_p = new char[m_len + 1];strcpy(m_p, s.m_p);return *this;}char& MyString::operator[] (int index)   //返回的是一个元素本身,char{return m_p[index];}//if (s2 == "s222222")bool MyString::operator==(const char *p) const{if (p == NULL){if (m_len == 0){return true;}else{return false;}}else{if (m_len == strlen(p)){return !strcmp(m_p, p);}else{return false;}}}bool MyString::operator!=(const char *p) const{return !(*this == p);}//if (s3 == s2)bool MyString::operator==(const MyString& s)  const{if (m_len != s.m_len){return false;}return !strcmp(m_p, s.m_p);}bool MyString::operator!=(const MyString& s) const{return !(*this == s);}//if (s3 < "bbbb")int MyString::operator<(const char *p){return strcmp(this->m_p , p);}int MyString::operator>(const char *p){return strcmp(p, this->m_p);}int MyString::operator<(const MyString& s){return strcmp(this->m_p , s.m_p);}int MyString::operator>(const MyString& s){return  strcmp(s.m_p, m_p);}
MyString_Test.cpp
#define _CRT_SECURE_NO_WARNINGS#include <iostream>using namespace std;#include "MyString.h"void main01(){MyString s1;MyString s2("s2");MyString s2_2 = NULL;MyString s3 = s2;MyString s4 = "s4444444444";// = 赋值运算符s4 = s2;s4 = "s2222";s4[1] = '4';    //支持数组的形式printf("%c", s4[1]);// << 操作符cout<<s4 <<endl;cout<<"hello..."<<endl;system("pause");return ;}void main02(){MyString s1;MyString s2("s2");MyString s3 = s2;// == 和 !=运算符if (s2 == "aa"){printf("相等");}else{printf("不相等");}if (s3 == s2){printf("相等");}else{printf("不相等");}}void main03(){MyString s1;MyString s2("s2");MyString s3 = s2;s3 = "aaa";// < 和 > 运算符int tag = (s3 < "bbbb");if (tag < 0 ){printf("s3 小于 bbbb");}else{printf("s3 大于 bbbb");}}void main011(){MyString s1(128);cout<<"\n请输入字符串(回车结束)";// >> 操作符cin>>s1;cout<<s1;system("pause");}

0 0
原创粉丝点击