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

时间:2021-12-11 15:06:55

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");
}
注意:拷贝构造函数同样需要使用初始化列表。
上述程序执行效果:

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

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

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构造函数 浅拷贝
//解决方案: 手工的编写拷贝构造函数,使用深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;
};

//对象析构的时候 出现coredump
void 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();
}
C++技术点积累(3)——对象初始化列表、运算符重载

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