博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++技术点积累(3)——对象初始化列表、运算符重载
阅读量:4124 次
发布时间:2019-05-25

本文共 11687 字,大约阅读时间需要 38 分钟。

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 
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 
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构造函数 浅拷贝 //解决方案: 手工的编写拷贝构造函数,使用深copy Name(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 
#include
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 
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 
#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_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 != a1.m_length) { return false; } for (int i=0; i
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 
#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

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

MyString.h

#pragma once#include 
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: int m_len; char *m_p;};
MyString.cpp
#define _CRT_SECURE_NO_WARNINGS#include "MyString.h"ostream& operator<<(ostream &out, MyString &s)//返回一个引用,这样函数返回值就可以当左值,支持连续输出,链式编程,流式概念{	out<
>(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 
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<
<
< 和 > 运算符 int tag = (s3 < "bbbb"); if (tag < 0 ) { printf("s3 小于 bbbb"); } else { printf("s3 大于 bbbb"); }}void main011(){ MyString s1(128); cout<<"\n请输入字符串(回车结束)"; // >> 操作符 cin>>s1; cout<

你可能感兴趣的文章
RedisTemplate的key默认序列化器问题
查看>>
序列化与自定义序列化
查看>>
ThreadLocal
查看>>
从Executor接口设计看设计模式之最少知识法则
查看>>
OKhttp之Call接口
查看>>
application/x-www-form-urlencoded、multipart/form-data、text/plain
查看>>
关于Content-Length
查看>>
WebRequest post读取源码
查看>>
使用TcpClient可避免HttpWebRequest的常见错误
查看>>
EntityFramework 学习之一 —— 模型概述与环境搭建 .
查看>>
C# 发HTTP请求
查看>>
启动 LocalDB 和连接到 LocalDB
查看>>
Palindrome Number --回文整数
查看>>
Reverse Integer--反转整数
查看>>
Container With Most Water --装最多水的容器(重)
查看>>
Longest Common Prefix -最长公共前缀
查看>>
Letter Combinations of a Phone Number
查看>>
Single Number II --出现一次的数(重)
查看>>
Valid Parentheses --括号匹配
查看>>
Remove Element--原地移除重复元素
查看>>