有关构造函数
1构造函数定义及调用
1)C++中的类可以定义与类名相同的特殊成员函数,这种与类名相同的成员函数叫做构造函数;
2)构造函数在定义时可以有参数;
3)没有任何返回类型的声明。
2构造函数的调用
自动调用:一般情况下C++编译器会自动调用构造函数
手动调用:在一些情况下则需要手工调用构造函数
有关析构函数
3)析构函数定义及调用
1)C++中的类可以定义一个特殊的成员函数清理对象,这个特殊的成员函数叫做析构函数
语法:~ClassName()
2)析构函数没有参数也没有任何返回类型的声明
3)析构函数在对象销毁时自动被调用
4)析构函数调用机制
C++编译器自动调用
代码演示:dm01_构造函数的基础.cpp
设计构造函数和析构函数的原因
面向对象的思想是从生活中来,手机、车出厂时,是一样的。
生活中存在的对象都是被初始化后才上市的;初始状态是对象普遍存在的一个状态的
普通方案:
为每个类都提供一个public的initialize函数;
对象创建后立即调用initialize函数进行初始化。
优缺点分析
1)initialize只是一个普通的函数,必须显示的调用
2)一旦由于失误的原因,对象没有初始化,那么结果将是不确定的
没有初始化的对象,其内部成员变量的值是不定的
3)不能完全解决问题
//为什么对象需要初始化 有什么样的初始化方案 #include "iostream" using namespace std;
/* 思考为什么需要初始化 面向对象思想来自生活,手机、车、电子产品,出厂时有初始化 怎么样进行初始化?
方案1:显示调用方法 缺点:易忘、麻烦;显示调用init,不能完全解决问题
*/ class Test21 { public: int m; int getM() const { return m; } void setM(int val) { m = val; }
int n; int getN() const { return n; } void setN(int val) { n = val; }
public: int init(int m,int n) { this->m = m; this->n = n; return 0; } protected: private: };
int main() { int rv =0; Test21 t1; //无参构造函数的调用方法 Test21 t2;
//t1.init(100, 200); //t2.init(300, 400); cout<&M()<<" "<&N()<<endl; cout<&M()<<" "<&N()<<endl;
//定义对象数组时,没有机会进行显示初始化 Test21 arr[3]; //Test arr_2[3] = {Test(1,3), Test(), Test()};
system("pause"); return rv; } |
//day03_类的构造和析构基础
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;class Test
{
public:Test() //无参构造函数{a = 10;p = (char *)malloc(100);strcpy(p,"aaaaffff");cout << "我是构造函数" << endl;}void print(){cout << p << endl;cout << a << endl;}~Test()//析构函数{if (p!=NULL){free(p);}cout << "我是析构函数" << endl;}private:int a;char *p;
};
//给对象搭建一个舞台 研究对象的行为
void display()
{//先创建的对象 后释放Test t1;t1.print();printf("分隔符n");Test t2;t2.print();
}int main()
{display();cout << " << endl;system("pause");return 0;
}
C++编译器给程序员提供的对象初始化方案,高端大气上档次。
//有参数构造函数的三种调用方法 class Test { private: int a; int b;
public:
//无参数构造函数 Test() { ; }
//带参数的构造函数 Test(int a, int b) { ; } //赋值构造函数 Test(const Test &obj) { ; }
public: void init(int _a, int _b) { a = _a; b = _b; } }; |
调用方法: Testt1, t2;
有参构造函数的三种调用方法
//有参数构造函数的三种调用方法 class Test5 { private: int a; public: //带参数的构造函数 Test5(int a) { printf("na:%d", a); } Test5(int a, int b) { printf("na:%d b:%d", a, b); } public: };
int main55() { Test5 t1(10); //c++编译器默认调用有参构造函数 括号法 Test5 t2 = (20, 10); //c++编译器默认调用有参构造函数 等号法 Test5 t3 = Test5(30); //程序员手工调用构造函数 产生了一个对象 直接调用构造构造函数法
system("pause"); return 0; } |
//day03_构造函数的分类
#include <iostream>
using namespace std;class Test2
{
public:Test2()//无参数构造函数{m_a = 0;m_b = 0;cout << "无参构造函数" << endl;}Test2(int a){m_a = a;m_b = 0;cout << "有参构造函数" << a << m_b << endl;}Test2(int a, int b)//3种方法{m_a = a;m_b = b;cout << "有参构造函数" << a << b << endl;}//copy构造函数Test2(const Test2 &obj){cout << "拷贝构造函数" << endl;}
public:void printT(){cout << "普通成员函数" << endl;}~Test2(){cout << "析构函数" << endl;}private:int m_a;int m_b;
};void main()
{//1括号法Test2 t1(1,2);//调用参数构造函数 c++编译器自动调用构造函数t1.printT();//2 =括号法 =c++对等号操作符 功能增强 c++编译器自动调用构造函数Test2 t2 = (3,4,5,6,7);Test2 t3 = 5;//3 直接调用构造函数 手动的调用构造函数Test2 t4 = Test2(3,4);//匿名对象 (匿名对象的去和留) 抛砖 ....//t4对象的初始化t1 = t4;//把t4copy 给t1 赋值操作//对象的初始化 和对象的赋值 是两个不同的概念cout << " << endl;system("pause");return;
}
赋值构造函数的四种调用场景(调用时机)
第1和第2个调用场景
//day03_copy构造函数的调用12
#include <iostream>
using namespace std;class Text
{
public:Text(){m_a = 0;m_b = 0;cout << "无参构造函数" << endl;}Text(int a){m_a = a;m_b = 0;cout << "有参构造函数" << a << m_b << endl;}Text(int a, int b){m_a = a;m_b = b;cout << "有参构造函数" << a << b << endl;}void print(){cout << m_a << m_b << endl;}~Text(){cout << "析构函数" << endl;}private:int m_a;int m_b;
};//1 赋值构造函数 用1个对象去初始化另外一个对象
void main()
{Text t1(1,2);Text t2(2,3);//赋值=操作 会不会调用构造函数//operator=()//抛砖t2 = t1;//用t1 给 t0赋值 到操作 和 初始化是两个不同的概念//第一种调用方法Text t3 = t1;//用t1来初始化 t3 t3.print();//第二种调用时机Text t4(t1); //用t1对象 初始化 t2对象 t4.print();cout << " << endl;system("pause");return;
}
//day03_copy构造函数的调用场景3
#include <iostream>
using namespace std;class Location
{
public:Location(){xx = 0;yy = 0;cout << "无参构造函数" << endl;}Location(int a, int b){xx = a;yy = b;cout << "有参构造函数:" << a << b << endl;}~Location(){cout << "析构函数" << endl;}int GetX(){return xx;}
private:int xx;int yy;
};void f(Location p)
{cout << p.GetX() << endl;
}//为了完整的展示一个对象的生命周期 搭建一个舞台
void playobj()
{Location L1(2, 3);Location L2 = L1;cout << "L2对象初始化完毕" << endl;f(L2);
}int main()
{//先调用L1的有参构造函数 再接着用L1初始化L2 调用了拷贝构造函数 L2对象初始化完毕//f(L2); //L2实参取初始化形参p,会调用copy构造函数 --(第三个调用时机)//调用函数完成后,会先析构形参p,接着析构掉L2,最后析构掉L1playobj();cout << " << endl;system("pause");return 0;
}
//day03_copy构造函数的第4种应用场景#include <iostream>
using namespace std;class Location
{
public:Location(int xx = 0, int yy = 0){X = xx; Y = yy; cout << "Constructor Object.n";}//copy构造函数 完成对象的初始化Location(const Location & obj) //copy构造函数 {X = obj.X; Y = obj.Y;}~Location(){cout << X << "," << Y << " Object destroyed." << endl;}int GetX() { return X; } int GetY() { return Y; }
private: int X, Y;
};//g函数 返回一个元素
//结论1 : 函数的返回值是一个元素 (复杂类型的), 返回的是一个新的匿名对象(所以会调用匿名对象类的copy构造函数)//
//结论2: 有关 匿名对象的去和留
//如果用匿名对象 初始化 另外一个同类型的对象, 匿名对象 转成有名对象
//如果用匿名对象 赋值给 另外一个同类型的对象, 匿名对象 被析构//
//你这么写代码,设计编译器的大牛们:
//我就给你返回一个新对象(没有名字 匿名对象)
Location g()
{Location A(1, 2);return A;
}//这里调用g()返回A时候,会调用拷贝构造函数
//g()函数 返回一个元素 返回一个新的匿名对象 故执行类的构造
//调用结束后 直接析构掉
void objplay2()
{g();
}//如果用匿名对象 初始化 另外一个同类型的对象 匿名对象转成有名对象 匿名对象不会被析构
void objplay3()
{//用匿名对象初始化m 此时c++编译器 直接把匿名对转成m;(扶正) 从匿名转成有名字了mLocation m = g();printf("匿名对象,被扶正,不会析构掉n");cout << m.GetX() << endl;;
}
//如果用匿名对象 赋值给 另外一个同类型的对象 匿名对象会被析构
void objplay4()
{//用匿名对象 赋值给 m2后, 匿名对象被析构Location m2(1, 2);m2 = g();printf("因为用匿名对象=给m2, 匿名对象,被析构n");cout << m2.GetX() << endl;;
}
void main()
{objplay2();objplay3();objplay4();cout << " << endl;system("pause");return;
}
二个特殊的构造函数
1)默认无参构造函数
当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空
2)默认拷贝构造函数
当类中没有定义拷贝构造函数时,编译器默认提供一个默认拷贝构造函数,简单的进行成员变量的值复制
1)当类中没有定义任何一个构造函数时,c++编译器会提供默认无参构造函数和默认拷贝构造函数
2)当类中定义了拷贝构造函数时,c++编译器不会提供无参数构造函数
3) 当类中定义了任意的非拷贝构造函数(即:当类中提供了有参构造函数或无参构造函数),c++编译器不会提供默认无参构造函数
4 )默认拷贝构造函数成员变量简单赋值
总结:只要你写了构造函数,那么你必须用。
构造析构阶段性总结
1)构造函数是C++中用于初始化对象状态的特殊函数
2)构造函数在对象创建时自动被调用
3)构造函数和普通成员函数都遵循重载规则
4)拷贝构造函数是对象正确初始化的重要保证
5)必要的时候,必须手工编写拷贝构造函数
========》1个对象的初始化讲完了,增加一个案例。
//day03_构造函数调用规则研究
#include <iostream>
using namespace std;class Text
{
public:Text(){cout << "无参构造函数" << endl;}Text(int a){m_a = a;m_b = 0;cout << "有参构造函数" << a << m_b << endl;}Text(int a, int b){m_a = a;m_b = b;cout << "有参构造函数" << a << b << endl;}void printT(){cout << "a:" << m_a << "b: " << m_b << endl;}~Text(){cout << "析构函数" << endl;}private:int m_a;int m_b;
};//当类中定义了拷贝构造函数时,c++编译器不会提供无参数构造函数
//当类中定义了有参数构造函数是,c++编译器不会提供无参数构造函数
//在定义类时, 只要你写了构造函数,则必须要用void playObj()
{Text t1;//调用无参构造函数
}
int main()
{playObj();cout << " << endl;system("pause");return 0;
}
Ø 默认复制构造函数可以完成对象的数据成员值简单的复制
Ø 对象的数据资源是由指针指示的堆时,默认复制构造函数仅作指针值复制
深拷贝浅拷贝现象出现的原因
显示提供copy构造函数
显示操作重载=号操作,不使用编译器提供的浅copy
class Name { public: Name(const char *pname) { size = strlen(pname); pName = (char *)malloc(size + 1); strcpy(pName, pname); } Name(Name &obj) { //用obj来初始化自己 pName = (char *)malloc(obj.size + 1); strcpy(pName, obj.pName); size = obj.size; } ~Name() { cout<<"开始析构"<<endl; if (pName!=NULL) { free(pName); pName = NULL; size = 0; } }
void operator=(Name &obj3) { if (pName != NULL) { free(pName); pName = NULL; size = 0; } cout<<"测试有没有调用我。。。。"<<endl;
//用obj3来=自己 pName = (char *)malloc(obj3.size + 1); strcpy(pName, obj3.pName); size = obj3.size; }
protected: private: char *pName; int size; };
//对象的初始化 和 对象之间=号操作是两个不同的概念 void playObj() { Name obj1("); Name obj2 = obj1; //obj2创建并初始化
Name obj3(");
//重载=号操作符 obj2 = obj3; //=号操作
cout<<"业务操作。。。5000"<<endl;
} void main61() { playObj(); system("pause"); } |
|
|
#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;//解决方案: 手工的编写拷贝构造函数 使用深copyName(const Name& obj1){m_len = obj1.m_len;m_p = (char *)malloc(m_len + 1);//为新的开辟内存空间 strcpy(m_p, obj1.m_p);}~Name(){if (m_p != NULL){free(m_p);m_p = NULL;m_len = 0;}}
protected:
private:char *m_p;int m_len;
};//对象析构的时候 出现coredump
//原因:浅拷贝 对同一空间发生两次析构,第二次析构定会出现问题
//解决办法:让每一个地址指向不同的空间,这样析构个析构自己的空间,就不会出现问题
void objplaymain()
{Name obj1("abcdefg");Name obj2 = obj1; //C++编译器提供的 默认的copy构造函数 浅拷贝Name obj3("obj3");//解决方法:显示重载等号操作符obj3 = obj1; // C++编译器提供的 等号操作 也属 浅拷贝
}void main()
{objplaymain();cout << " << endl;system("pause");return;
}
1)对象初始化列表出现原因
1.必须这样做:
如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,没有默认构造函数。这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数,
如果没有初始化列表,那么他将无法完成第一步,就会报错。
2、类成员中若有const修饰,必须在对象初始化的时候,给const int m 赋值
当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化,
因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的。
2)C++中提供初始化列表对成员变量进行初始化
语法规则
Constructor::Contructor() : m1(v1),m2(v1,v2), m3(v3)
{
// some other assignment operation
}
3)注意概念
初始化:被初始化的对象正在创建
赋值:被赋值的对象已经存在
4)注意:
成员变量的初始化顺序与声明的顺序相关,与在初始化列表中的顺序无关
初始化列表先于构造函数的函数体执行
/* 1 C++中提供了初始化列表对成员变量进行初始化 2 使用初始化列表出现原因: 1.必须这样做: 如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数, 而没有默认构造函数,这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数, 如果没有初始化列表,那么他将无法完成第一步,就会报错。
2、类成员中若有const修饰,必须在对象初始化的时候,给const int m 赋值 当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化, 因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的。 //day03_构造函数初始化列表
|
构造函数与析构函数的调用顺序
1)当类中有成员变量是其它类的对象时,首先调用成员变量的构造函数,调用顺序与声明顺序相同;之后调用自身类的构造函数
2)析构函数的调用顺序与对应的构造函数调用顺序相反
通过训练,把所学知识点都穿起来
demo10_构造析构练习强化.cpp (讲解)
//对象做函数参数
//1 研究拷贝构造
//2 研究构造函数,析构函数的调用顺序//总结 构造和析构的调用顺序#include "iostream"
using namespace std;class ABCD
{
public:ABCD(int a, int b, int c){this->a = a;this->b = b;this->c = c;printf("ABCD() construct, a:%d,b:%d,c:%d n", this->a, this->b, this->c);}~ABCD(){printf("~ABCD() construct,a:%d,b:%d,c:%d n", this->a, this->b, this->c);}int getA(){return this->a;}
protected:
private:int a;int b;int c;
};class MyE
{
public:MyE() :abcd1(1, 2, 3), abcd2(4, 5, 6), m(100){cout << "MyD()" << endl;}~MyE(){cout << "~MyD()" << endl;}MyE(const MyE &obj) :abcd1(7, 8, 9), abcd2(10, 11, 12), m(100){printf("MyD(const MyD & obj)n");}protected://private:
public:ABCD abcd1; //c++编译器不知道如何构造abc1ABCD abcd2;const int m;};int doThing(MyE mye1)
{printf("doThing() mye1.abc1.a:%d n", A());return 0;
}int run2()
{MyE myE;doThing(myE);return 0;
}//
int run3()
{printf("run3 start..n");//产生一个构造函数 和 一个析构函数//因为产生匿名对象 没有人接 直接析构掉//ABCD(400, 500, 600); //临时对象的生命周期 //run3 start..// ABCD() construct, a:400, b : 500, c : 600// ~ABCD() construct, a : 400, b : 500, c : 600// run3 end// 请按任意键继续. . .ABCD abcd = ABCD(100, 200, 300);//run3 start..// ABCD() construct, a:100, b : 200, c : 300// run3 end// ~ABCD() construct, a : 100, b : 200, c : 300// 请按任意键继续. . .//若直接调用构造函数呢?//想调用构造函数对abc对象进行再复制,可以吗?//在构造函数里面调用另外一个构造函数,会有什么结果?printf("run3 endn");return 0;
}int main()
{//run2();//构造析综合训练/*
初始化列表 用来const属性赋值
先执行 被组合对象的构造函数
如果组合对象有多个,按照定义顺序 而不是按照初始化列表的顺序
printf("ABCD() construct, a:%d,b:%d,c:%d n", this->a, this->b, this->c);1 2 3
printf("ABCD() construct, a:%d,b:%d,c:%d n", this->a, this->b, this->c);4 5 6
cout << "MyD()" << endl;
copy构造函数的调用场景3 //实参初始化形参p,会调用copy构造函数 --(第三个调用时机
printf("ABCD() construct, a:%d,b:%d,c:%d n", this->a, this->b, this->c);7 8 9
printf("ABCD() construct, a:%d,b:%d,c:%d n", this->a, this->b, this->c);10 11 12
printf("MyD(const MyD & obj)n");printf("doThing() mye1.abc1.a:%d n", A());7开始析构形参mye1cout << "~MyD()" << endl;
析构函数:和构造函数的调用顺序相反printf("~ABCD() construct,a:%d,b:%d,c:%d n", this->a, this->b, this->c);10 11 12printf("~ABCD() construct,a:%d,b:%d,c:%d n", this->a, this->b, this->c);7 8 9cout << "~MyD()" << endl;
开始析构形参myEprintf("~ABCD() construct,a:%d,b:%d,c:%d n", this->a, this->b, this->c);4 5 6printf("~ABCD() construct,a:%d,b:%d,c:%d n", this->a, this->b, this->c);1 2 3*/run3();//匿名对象强化训练system("pause");return 0;
}
1)在软件开发过程中,常常需要动态地分配和撤销内存空间,例如对动态链表中结点的插入与删除。在C语言中是利用库函数malloc和free来分配和撤销内存空间的。C++提供了较简便而功能较强的运算符new和delete来取代malloc和free函数。
注意: new和delete是运算符,不是函数,因此执行效率高。
2)虽然为了与C语言兼容,C++仍保留malloc和free函数,但建议用户不用malloc和free函数,而用new和delete运算符。new运算符的例子:
new int; //开辟一个存放整数的存储空间,返回一个指向该存储空间的地址(即指针)
new int(100); //开辟一个存放整数的空间,并指定该整数的初值为100,返回一个指向该存储空间的地址
new char[10]; //开辟一个存放字符数组(包括10个元素)的空间,返回首元素的地址
new int[5][4]; //开辟一个存放二维整型数组(大小为5*4)的空间,返回首元素的地址
float *p=new float (3.14159); //开辟一个存放单精度数的空间,并指定该实数的初值为//3.14159,将返回的该空间的地址赋给指针变量p
3)new和delete运算符使用的一般格式为:
用new分配数组空间时不能指定初值。如果由于内存不足等原因而无法正常分配空间,则new会返回一个空指针NULL,用户可以根据该指针的值判断分配空间是否成功。
1) 应用举例
使用类名定义的对象都是静态的,在程序运行过程中,对象所占的空间是不能随时释放的。但有时人们希望在需要用到对象时才建立对象,在不需要用该对象时就撤销它,释放它所占的内存空间以供别的数据使用。这样可提高内存空间的利用率。
C++中,可以用new运算符动态建立对象,用delete运算符撤销对象
比如:
Box *pt; //定义一个指向Box类对象的指针变量pt
pt=new Box; //在pt中存放了新建对象的起始地址
在程序中就可以通过pt访问这个新建的对象。如
cout<<pt->height; //输出该对象的height成员
cout<<pt->volume( ); //调用该对象的volume函数,计算并输出体积
C++还允许在执行new时,对新建立的对象进行初始化。如
Box *pt=new Box(12,15,18);
这种写法是把上面两个语句(定义指针变量和用new建立新对象)合并为一个语句,并指定初值。这样更精炼。
新对象中的height,width和length分别获得初值12,15,18。调用对象既可以通过对象名,也可以通过指针。
在执行new运算时,如果内存量不足,无法开辟所需的内存空间,目前大多数C++编译系统都使new返回一个0指针值。只要检测返回值是否为0,就可判断分配内存是否成功。
ANSI C++标准提出,在执行new出现故障时,就“抛出”一个“异常”,用户可根据异常进行有关处理。但C++标准仍然允许在出现new故障时返回0指针值。当前,不同的编译系统对new故障的处理方法是不同的。
在不再需要使用由new建立的对象时,可以用delete运算符予以释放。如
delete pt; //释放pt指向的内存空间
这就撤销了pt指向的对象。此后程序不能再使用该对象。
如果用一个指针变量pt先后指向不同的动态对象,应注意指针变量的当前指向,以免删错了对象。在执行delete运算符时,在释放内存空间之前,自动调用析构函数,完成有关善后清理工作。
//1 mallocfree函数 c关键字
// newdelete 操作符号 c++的关键字
//2 new 在堆上分配内存 delete
//分配基础类型 、分配数组类型、分配对象
//3 new和malloc 深入分析
混用测试、异同比较
结论: malloc不会调用类的构造函数
Free不会调用类的析构函数
//new和delete基本语法#include <iostream>
using namespace std;//分配基础类型int main0301()
{int *p = (int *)malloc(sizeof(int));*p = 10;free(p);int *p2 = new int;//分配类型*p2 = 20;free(p2);int *p3 = new int(30);printf("*p3:%d",*p3);delete p3;system("pause");return 0;
}void main0302()
{//c语言数组变量int *p = (int *)malloc(sizeof(int) * 10);p[0] = 1;free(p);//c++分配数组int *pArray = new int[10];pArray[1] = 2;delete[] pArray;//数组不要把[] 忘记char *pArray2 = new char[25];//char buf[25]delete[] pArray2;cout << " << endl;system("pause");return;
}class Text
{
public:Text(int m_a){a = m_a;cout << "构造函数执行" << endl;}~Text(){cout << "析构函数执行" << endl;}private:int a;
};//分配对象 new delete
//相同 和 不同的地方 new能执行类型构造函数 delete操作符 能执行类的析构函数void main()
{//cText *pT1 = (Text *)malloc(sizeof(Text));free(pT1);//c++Text *pT2 = new Text(10);delete pT2;cout << " << endl;system("pause");
}
//day03_new和delete深入分析
#include <iostream>
using namespace std;// 1
// malloc free c语言的函数
// new delete 操作符 c++的语法//2 new 基础类型变量 分配数组变量 分配类对象//3 分配基础类型 两个混用 没什么问题
void main()
{// int *p = (int *)malloc(sizeof(int));*p = 10;//free(p);delete p;int *p2 = new int; //分配基础类型*p2 = 20;free(p2);//int *p3 = new int(30);printf("*p3:%d n", *p3);//delete p3;free(p3);cout << " << endl;system("pause");return;
}//分配数组变量
void main0302()
{//c语言分配数组int *p = (int *)malloc(sizeof(int) * 10); //int array[10];p[0] = 1;//free(p);delete[] p;//c++分配数组 int *pArray = new int[10];pArray[1] = 2;//delete [] pArray; //数组不要把[] 忘记free(pArray);char *pArray2 = new char[25]; //char buf[25]delete[] pArray2;cout << " << endl;system("pause");return;
}class Test
{
public:Test(int _a){a = _a;cout << "构造函数执行" << endl;}~Test(){cout << "析构函数执行" << endl;}protected:
private:int a;
};//分配对象new delete
//相同 和 不同的地方 new不仅能分配内存,还能执行类型构造函数 完成初始化 delete操作符 能执行类的析构函数// malloc free函数 C
//1 new delete 操作符号 c++的关键字
//结论
//3 new和malloc 深入分析
//混用测试、异同比较
//结论: malloc不会调用类的构造函数
//Free不会调用类的析构函数void main0303()
{//c Test *pT1 = (Test *)malloc(sizeof(Test));free(pT1);//delete pT1;//c++Test *pT2 = new Test(10);delete pT2;//free(pT2);cout << " << endl;system("pause");
}
思考:每个变量,拥有属性。有没有一些属性,归所有对象拥有?
1)定义静态成员变量
Ø 关键字 static 可以用于说明一个类的成员,
静态成员提供了一个同类对象的共享机制
Ø 把一个类的成员说明为 static 时,这个类无论有多少个对象被创建,这些对象共享这个 static 成员
Ø 静态成员局部于类,它不是对象成员
例如:
#include<iostream>
using namespace std;
class counter
{
static int num ; //声明与定义静态数据成员
public :
void setnum ( int i ) { num = i ;} //成员函数访问静态数据成员
void shownum() { cout <<num << 't' ; }
} ;
int counter :: num = 0 ;//声明与定义静态数据成员
void main ()
{ counter a , b ;
a.shownum() ; //调用成员函数访问私有静态数据成员
b.shownum() ;
a.setnum(10) ;
a.shownum() ;
b.shownum() ;
}
从结果可以看出,访问的是同一个静态数据成员
2)使用静态成员变量
// 例5-14 使用公有静态数据成员
#include<iostream.h>
class counter
{ public :
counter (int a) { mem = a; }
int mem; //公有数据成员
static int Smem ; //公有静态数据成员
} ;
int counter :: Smem = 1 ; //初始值为1
void main()
{ counter c(5);
int i ;
for( i = 0 ; i < 5 ; i ++ )
{ counter::Smem += i ;
cout << counter::Smem << 't' ; //访问静态成员变量方法2
}
cout<<endl;
cout<<"c.Smem = "<<c.Smem<<endl; //访问静态成员变量方法1
cout<< = "<<<<<endl;
}
1)概念
Ø 静态成员函数数冠以关键字static
Ø 静态成员函数提供不依赖于类数据结构的共同操作,它没有this指针
Ø 在类外调用静态成员函数用“类名 :: ”作限定词,或通过对象调用
2)案例
3)疑难问题:静态成员函数中,不能使用普通变量。
//静态成员变量属于整个类的,分不清楚,是那个具体对象的属性。
//静态成员变量成员函数
#include <iostream>
using namespace std;class BB
{
public:void printC(){cout << "c:" << c << endl;}void ADDC(){c = c + 1;}//静态成员函数:// 静态成员函数数冠以关键字static// 静态成员函数提供不依赖于类数据结构的共同操作,它没有this指针// 在类外调用静态成员函数用 “类名::”作限定词,或通过对象调用static void getC(){cout << "c:" << c << endl;//请在静态成员函数中,能调用 普通成员属性 或者 普通成员函数吗?//错误 1 error C2597: 对非静态成员“BB::a”的非法引用//cout << "a:" << a << endl;}private:int a;int b;static int c;
};
//声明与定义静态数据成员 必须要初始化
//定义静态成员变量
// 关键字 static 可以用于说明一个类的成员,
//静态成员提供了一个同类对象的共享机制
// 把一个类的成员说明为 static 时,这个类无论有多少个对象被创建,这些对象共享这个 static 成员
// 静态成员局部于类,它不是对象成员int BB::c = 10;void main()
{BB b1, b2, b3;b1.printC(); //10b2.ADDC(); //11b3.printC(); //11//静态成员函数的调用方法b3.getC(); //用对象.BB::getC();//类::cout << " << endl;system("pause");return;}
前言
C++对象模型可以概括为以下2部分:
1. 语言中直接支持面向对象程序设计的部分,主要涉及如构造函数、析构函数、虚函数、继承(单继承、多继承、虚继承)、多态等等。
2. 对于各种支持的底层实现机制。
在c语言中,“数据”和“处理数据的操作(函数)”是分开来声明的,也就是说,语言本身并没有支持“数据和函数”之间的关联性。在c++中,通过抽象数据类型(abstractdata type,ADT),在类中定义数据和函数,来实现数据和函数直接的绑定。
概括来说,在C++类中有两种成员数据:static、nonstatic;三种成员函数:static、nonstatic、virtual。
C++中的class从面向对象理论出发,将变量(属性)和函数(方法)集中定义在一起,用于描述现实世界中的类。从计算机的角度,程序依然由数据段和代码段构成。 C++编译器如何完成面向对象理论到计算机程序的转化? 换句话:C++编译器是如何管理类、对象、类和对象之间的关系 具体的说:具体对象调用类中的方法,那,c++编译器是如何区分,是那个具体的类,调用这个方法那? |
思考一下程序结果 |
#include "iostream"
using namespace std;
class C1 { public: int i; //4 int j; //4 int k; //4 protected: private: }; //12
class C2 { public: int i; int j; int k;
static int m; //4 public: int getK() const { return k; } //4 void setK(int val) { k = val; } //4
protected: private: }; //24 16 12(铁钉的不对)
struct S1 { int i; int j; int k; }; //
struct S2 { int i; int j; int k; static int m; }; //
int main() { printf("c1:%d n", sizeof(C1)); printf("c2:%d n", sizeof(C2)); printf("s1:%d n", sizeof(S1)); printf("s2:%d n", sizeof(S2));
system("pause"); } |
通过上面的案例,我们可以的得出: 1)C++类对象中的成员变量和成员函数是分开存储的 成员变量: 普通成员变量:存储于对象中,与struct变量有相同的内存布局和字节对齐方式 静态成员变量:存储于全局数据区中 成员函数:存储于代码段中。 问题出来了:很多对象共用一块代码?代码是如何区分具体对象的那?
换句话说:int getK() const { return k; },代码是如何区分,具体obj1、obj2、obj3对象的k值?
|
2)C++编译器对普通成员函数的内部处理 |
1、C++类对象中的成员变量和成员函数是分开存储的。C语言中的内存四区模型仍然有效! 2、C++中类的普通成员函数都隐式包含一个指向当前对象的this指针。 3、静态成员函数、成员变量属于类 静态成员函数与普通成员函数的区别 静态成员函数不包含指向具体对象的指针 普通成员函数包含一个指向具体对象的指针 |
//day03_面向对象模型初探
#include "iostream"
using namespace std;class C1
{
public:int i; //4int j; //4int k; //4
protected:
private:
}; //12class C2
{
public:int i;int j;int k;static int m; //4
public:int getK() const { return k; } //4void setK(int val) { k = val; } //4protected:
private:
}; //24 16 12(铁钉的不对)struct S1
{int i;int j;int k;
}; //12struct S2
{int i;//4int j;//4int k;//4static int m;//4
}; //16int main()
{printf("c1:%d n", sizeof(C1));printf("c2:%d n", sizeof(C2));printf("s1:%d n", sizeof(S1));printf("s2:%d n", sizeof(S2));
//c1:12
//c2 : 12
// s1 : 12
// s2 : 12
// 请按任意键继续. . .system("pause");
}
实验1:若类成员函数的形参和 类的属性,名字相同,通过this指针来解决。
实验2:类的成员函数可通过const修饰,请问const修饰的是谁
//day03_面向对象模型this指针练习
#include <iostream>
using namespace std;
class Test
{
public://实验1:若类成员函数的形参 和 类的属性,名字相同,通过this指针来解决。Test(int a, int b) //---> Test(Test *this, int a, int b){this->a = a;this->b = b;}void printT(){cout << "a: " << a << endl;cout << "b: " << this->b << endl;}
protected:
private:int a;int b;
};void main()
{Test t1(1, 2);t1.printT();// ===> printT(&t1)cout << " << endl;system("pause");return;
}
//day03_面向对象模型this指针练习
#include <iostream>
using namespace std;
class Test
{
public://实验1:若类成员函数的形参 和 类的属性,名字相同,通过this指针来解决。Test(int a, int b) //---> Test(Test *this, int a, int b){this->a = a;this->b = b;}void printT(){cout << "a: " << a << endl;cout << "b: " << this->b << endl;}//实验2:类的成员函数可通过const修饰,请问const修饰的是谁//1 const 写的什么位置 没有关系 //void const Opvar(int a, int b) //const void Opvar(int a, int b)// 2-1const修饰的是形参a 不是void Opvar(int a, int b) const//==>void Opvar(const Test *this,int a,int b)//==》void Opvar(const Test *const this,int a,int b){ a = 100;//发现能被修改,说明const修饰的不是形参a// 2-2const修饰的是属性this->a this->b //this->a = 13;//发现不能被修改//this->b = b;//发现不能被修改// 2-3 const修饰的是this指针所指向的内存空间, 修饰的是this指针//this = 0x11;//不能被修改}
protected:
private:int a;int b;
};void main()
{Test t1(1, 2);t1.printT();// ===> printT(&t1)cout << " << endl;system("pause");return;
}
1、把全局函数转化成成员函数,通过this指针隐藏左操作数
Testadd(Test &t1, Test &t2)===》Test add(Test&t2)
2、把成员函数转换成全局函数,多了一个参数
voidprintAB()===》void printAB(Test *pthis)
3、函数返回元素和返回引用
Test& add(Test &t2) //*this //函数返回引用
{
this->a = this->a A();
this->b = this->b B();
return*this; //*操作让this指针回到元素状态
}
Test add2(Test &t2)//*this //函数返回元素
{
//t3是局部变量
Test t3(this->aA(),this->b + t2.getB()) ;
return t3;
}
day03_面向对象模型_全局函数成员函数
#include <iostream>
using namespace std;class Test
{
public:Test(){this->a = 0;this->b = 0;}Test(int a, int b){this->a = a;this->b = b;}~Test(){cout << "a:" << a << " b: " << b;cout << "析构函数自动被调用" << endl;}
public:void printT(){cout << "a:" << a << " b: " << b << endl;}//t3 = t1.TestAdd(t2);Test TestAdd(Test &t2){Test tmp(this->a+t2.a,this->b+t2.b);return tmp;}//t1.TestAdd2(t2);//返回一个引用 相当于返回自身 //返回t1这个元素 this就是&t1//因此返回一个引用,程序员只需要返回变量自身就行了Test& TestAdd2(Test &t2){this->a = this->a + t2.a;this->b = this->b + t2.b;return *this; //把 *(&t1) 又回到了 t1元素 *操作让this指针回到元素状态 C++编译器会将返回的自身变成一个引用}
public:int a;int b;
};
//全局函数 转成成员函数 多一个参数
Test TestAdd(Test &t1, Test &t2)
{Test tmp(t1.a+t2.a,t1.b+t2.b);return tmp;
}
void main()
{Test t1(1, 2);Test t2(3, 4);Test t3;//t1 = t1 + t2;
//返回引用就能实现返回对象自身 //这样函数在调用自身的时候返回的还是自身的引用还能再次的使用相应的函数t1.TestAdd2(t2).TestAdd2(t2).TestAdd2(t2);t1.printT();//全局函数方法t3 = TestAdd(t1, t2);//成员函数方法{//先把测试案例写出来//匿名对象 初始化给同类型对象 直接转成有名对象Test t4 = t1.TestAdd(t2); //匿名对象直接转化成t4t4.printT();Test t5;t5 = t1.TestAdd(t2); //匿名对象 复制 给t5//匿名对象 赋值给同类型对象 匿名对象被析构t5.printT();}cout << " << endl;system("pause");return;
}
目标:解决实际问题,训练构造函数、copy构造函数等,为操作符重载做准备
//myarray.h
#pragma once
#include <iostream>
using namespace std;class myarray
{
public:myarray(int length);myarray(const myarray& obj);~myarray();public:void setData(int index, int value);int getData(int index);int length();
private:int m_length;int *m_space;
};
//myarray.cpp
#include "myarray.h"//int m_length;
//int *m_space;
myarray::myarray(int length)
{if (length < 0){length = 0;}m_length = length;m_space = new int[m_length];//int myarray.m_space[m_length]
}
//Array a2 = a1;
myarray::myarray(const myarray& 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];}
}myarray::~myarray()
{if (m_space != NULL){delete[] m_space;m_space = NULL;m_length = 0;}
}void myarray::setData(int index, int value)
{this->m_space[index] = value;
}int myarray::getData(int index)
{return this->m_space[index];
}int myarray::length()
{return this->m_length;
}
//myarray_text.cpp
#include <iostream>
#include "myarray.h"using namespace std;//类的框架设计完毕
//类的测试案例int main()
{myarray a1(10);for (int i = 0; i < a1.length(); i++){a1.setData(i,i);}cout << "n打印数组a1:n";for (int i = 0; i < a1.length(); i++){cout << a1.getData(i) << " ";}cout << "n==================分界线=================n";myarray a2 = a1;cout << "n打印数组a2:n";for (int i = 0; i < a2.length(); i++){cout << a2.getData(i) << " ";}cout << "n==================分界线=================n";cout << " << endl;system("pause");return 0;
}
#include <iostream>
using namespace std;class AA
{friend class B;//B类 是 A的好朋友 ,在B中可以访问A类的私有成员 私有函数//友元的声明仅仅指定了访问的权限 而非通常意义上的函数声明 所以需要在友元声明之外再对函数进行一次声明//一般来说,最好的在类定义开始或者结束前的位置集中声明友元friend void modifyA(AA *pA, int m_a); //1 函数modifyA 是 类A的好朋友
public:AA(int a = 0, int b = 0){this->a = a;this->b = b;cout << "有参构造函数" << endl;}int getA(){return this->a;}~AA(){cout << "~AA析构函数" << endl;}private:int a;int b;
};class B
{
public:void Set(int a){Aobject.a = a;}void printB(){cout << Aobject.a << endl;}
private:AA Aobject;//B类 是 A的好朋友 ,在B中可以访问A类的私有成员 私有函数
};//友元函数
void modifyA(AA *pA, int m_a)
{//pA->a = 100;pA->a = m_a;
}void main()
{B b1;b1.Set(300);b1.printB();system("pause");
}
void main0301()
{AA a1(1,2);cout << a1.getA() << endl;modifyA(&a1,300);cout << a1.getA() << endl;}
#include <iostream>
using namespace std;class AA
{
private:int i;//声明一个友元函数friend void FriendFun(AA *ptr, int x); //说明语句位置与访问描述无关
public:int MemberFun();};void FriendFun(AA *ptr, int x)//说明语句位置与访问描述无关
{ptr->i = x;
}int AA::MemberFun()
{return i;
}void main0301()
{AA mya1;cout << mya1.MemberFun() << endl;FriendFun(&mya1, 300); //通过友元函数 修改A类的私有属性cout << mya1.MemberFun() << endl;system("pause");
}
//用友元函数计算两点之间的距离
class Point
{friend double Distance(Point& a, Point& b);//用友元函数计算两点之间的距离
public:Point(double x, double y){this->x = x;this->y = y;}double GetX(){return x;}double GetY(){return y;}private:double x, y;
};double Distance(Point& a, Point& b)
{double dx = a.x - b.x;double dy = a.y - b.y;return sqrt(dx*dx + dy*dy);
}void main()
{Point p1(1, 1), p2(1,1);double distance = Distance(p1,p2);cout << "The distance is:" << distance << endl;system("pause");}
Ø 类通常用关键字class定义。类是数据成员和成员函数的封装。类的实例称为对象。
Ø 结构类型用关键字struct定义,是由不同类型数据组成的数据类型。
Ø 类成员由private, protected, public决定访问特性。public成员集称为接口。
Ø 构造函数在创建和初始化对象时自动调用。析构函数则在对象作用域结束时自动调用。
Ø 重载构造函数和复制构造函数提供了创建对象的不同初始化方式。
Ø 静态成员是局部于类的成员,提供一种同类对象的共享机制。
Ø 友员用关键字friend声明。友员是对类操作的一种辅助手段。一个类的友员可以访问该类各种性质的成员。
Ø 链表是一种重要的动态数据结构,可以在程序运行时创建或撤消数据元素。
所谓重载,就是重新赋予新的含义。函数重载就是对一个已有的函数赋予新的含义,使之实现新功能,因此,一个函数名就可以用来代表不同功能的函数,也就是”一名多用”。
运算符也可以重载。实际上,我们已经在不知不觉之中使用了运算符重载。例如,大家都已习惯于用加法运算符”+”对整数、单精度数和双精度数进行加法运算,如5+8, 5.8 +3.67等,其实计算机对整数、单精度数和双精度数的加法操作过程是很不相同的,但由于C++已经对运算符”+”进行了重载,所以就能适用于int, float, doUble类型的运算。
又如”<<“是C++的位运算中的位移运算符(左移),但在输出操作中又是与流对象cout 配合使用的流插入运算符,”>>“也是位移运算符(右移),但在输入操作中又是与流对象 cin 配合使用的流提取运算符。这就是运算符重载(operator overloading)。C++系统对”<<“和”>>“进行了重载,用户在不同的场合下使用它们时,作用是不同的。对”<<“和”>>“的重载处理是放在头文件stream中的。因此,如果要在程序中用”<< “和”>>”作流插入运算符和流提取运算符,必须在本文件模块中包含头文件stream(当然还应当包括”using namespace std“)。
现在要讨论的问题是:用户能否根据自己的需要对C++已提供的运算符进行重载,赋予它们新的含义,使之一名多用。?
1为什么会用运算符重载机制
用复数类举例
//Complex c3 =c1 + c2;
//原因 Complex是用户自定义类型,编译器根本不知道如何进行加减
//编译器给提供了一种机制,让用户自己去完成,自定义类型的加减操作。。。。。
//这个机制就是运算符重载机制
2 运算符重载的本质是一个函数
//day03_运算符重载入门技术推演
#include <iostream>
using namespace std;class Complex
{
public:Complex(int a = 0, int b = 0){this->a = a;this->b = b;}void printCom(){cout << a << " + " << b << "i" << endl;}public:int a;int b;
};
//1 定义全局函数
Complex myAdd(Complex &c1, Complex& c2)
{Complex tmp(c1.a+c2.a,c1.b+c2.b);return tmp;
}
//2 函数名 升级
Complex operator+(Complex &c1, Complex &c2)
{cout << "12345上山 打老虎" << endl;Complex tmp(c1.a + c2.a, c1.b + c2.b);return tmp;
}
int main()
{int a = 0, b = 0;int c;c = a + b;//1 基础数据类型 编译器已经知道了. 如何运算//2 a + bi 复数运算规则Complex c1(1, 2), c2(3, 4);Complex c3; //2 类 也是一种数据类型 用户自定义数据类型 C++编译器 是不知道如何进行运算//c3 = c1 + c2 ;//c1--; --c1//3 c++编译器应该给我们程序员提供一种机制 ... //让自定义数据类型 有机会 进行 运算符操作 ====> 运算符重载机制 //4 运算符重载机制//步骤1Complex c4 = myAdd(c1,c2);c4.printCom();//步骤2Complex c5 = operator+(c1,c2);c5.printCom();//步骤3 Complex c6 = c1 + c2;c6.printCom();//总结: 1 运算符重载的本质 是 函数调用 cout << " << endl;system("pause");return;
}
注意:一个运算符被重载后,原有意义没有失去,只是定义了相对一特定
类的新元素符
例如:
//全局函数 完成 +操作符 重载
Complex operator+(Complex &c1, Complex&c2)
//类成员函数 完成 -操作符 重载
Complex operator-(Complex &c2)
#include <iostream>
using namespace std;
//二元运算符 ObjectL op ObjectR
//重载为成员函数:ObjectL.operator op (ObjectR) 左边操作由ObjectL通过this指针传递 右操作数由参数ObjectR传递//重载为友元函数:operator op( ObjectL , ObjectR) 左右参数都由参数传递
class Complex
{//全局函数 重载+运算符friend Complex operator+(Complex &c1, Complex &c2);
public:Complex(int a = 0, int b = 0){this->a = a;this->b = b;}void printCom(){cout << a << " + " << b << "i" << endl;}~Complex(){cout << "析构函数" << endl;}
public:Complex operator-(Complex &c2){Complex tmp(this->a-c2.a,this->b-c2.b);return tmp;}
private:int a;int b;
};//全局函数 重载+运算符
Complex operator+(Complex &c1, Complex &c2)
{Complex tmp(c1.a+c2.a,c1.b+c2.b);return tmp;
}
/*
全局函数、类成员函数方法实现运算符重载步骤
1)要承认操作符重载是一个函数,写出函数名称
2)根据操作数,写出函数参数
3)根据业务,完善函数返回值(看函数是返回引用 还是指针 元素),及实现函数业务
*/
void main()
{Complex c1(1, 2), c2(3, 4);//1 全局函数法 实现 + 运算符重载// Complex operator+(Complex &c1, Complex &c2);Complex c3 = c1 + c2;c3.printCom();//2 成员函数法 实现 - 运算符重载Complex c4 = c1 - c2;//c1.operator-(c2);//Complex operator-(Complex &c2);c4.printCom();cout << " << endl;system("pause");return;
}
//重载二元运算符* /
#include <iostream>
using namespace std;//二元运算符 ObjectL op ObjectR
//重载为成员函数:ObjectL.operator op (ObjectR) 左边操作由ObjectL通过this指针传递 右操作数由参数ObjectR传递//重载为友元函数:operator op( ObjectL , ObjectR) 左右参数都由参数传递
class Complex
{friend Complex operator*(Complex &c1, Complex &c2);
public:Complex(int a = 0, int b = 0){this->a = a;this->b = b;}void printCom(){cout << a << " + " << b << "i" << endl;}public:Complex operator/(Complex &c1){Complex tmp(this->a / c1.a, this->b / c1.b);return tmp;}private:int a;int b;
};
//全局函数法
Complex operator*(Complex &c1, Complex &c2)
{Complex tmp(c1.a * c2.a,c1.b * c2.b);return tmp;
}void main()
{int a = 3, b = 4;int c = a*b;Complex c1(1, 2), c2(3,4);Complex c3 = c1 * c2;//全局函数法 实现*运算符重载//operator *(c1, c2)//Complex operator*(Complex &c1,Complex &c2);c3.printCom();c3 = c2 / c1;//成员函数法 实现/运算符重载//c2.opertaor / (c1);//Complex operator/(Complex &c1);c3.printCom();cout << " << endl;system("pause");return;
}
例如1:
//通过类成员函数完成-操作符重载
//函数声明 Complexoperator-(Complex &c2)
//函数调用分析
//用类成员函数实现-运算符重载
Complex c4 = c1 - c2;
c4.printCom();
//c1.operator-(c2);
例如2:
//通过全局函数方法完成+操作符重载
//函数声明 Complexoperator+(Complex &c1, Complex &c2)
//函数调用分析
int main()
{
Complex c1(1, 2), c2(3, 4);
//Complex c31 = operator+(c1,c2);
Complex c3 = c1 + c2;
c3.printCom();
}
例如3: 学员自己练习 实现 * /
例如3
//前置++操作符 用全局函数实现
Complex&operator++(Complex &c1)
{
c1.a ++;
c1.b ++;
return c1;
}
//调用方法
++c1 ; //=è需要写出操作符重载函数原形
c1.printCom();
//运算符重载函数名定义
//首先承认操作符重载是一个函数 定义函数名èoperator++
//分析函数参数 根据左右操作数的个数,èoperator++(Complex &c1)
//分析函数返回值è Complex&operator++(Complex &c1) 返回它自身
例如4
//4.1前置—操作符 成员函数实现
Complex&operator--()
{
this->a--;
this->b--;
return *this;
}
//4.2调用方法
--c1;
c1.printCom();
//4.3前置—运算符重载函数名定义
//c1.operator--()
例如5
//5.1//后置++ 操作符用全局函数实现
Complex operator++(Complex &c1, int)
{
Complex tmp = c1;
c1.a++;
c1.b++;
return tmp;
}
//5.2 调用方法
c1 ++ ; //先使用后++
//5.3 后置++运算符重载函数名定义
Complex operator++(Complex&c1, int) //函数占位参数 和 前置++ 相区别
例如6
//6.1 后置— 操作符 用类成员函数实现
Complexoperator--(int)
{
Complextmp = *this;
this->a--;
this->b--;
returntmp;
}
//6.2 调用方法
c1 ++ ; //先使用后++
//6.3 后置--运算符重载函数名定义
Complex operator--(int) //函数占位参数和 前置-- 相区别
前置和后置运算符总结
C++中通过一个占位参数来区分前置运算和后置运算
#include <iostream>
using namespace std;
//一元运算符 Object op 或者 op Object
//重载为成员函数:Object.operator op() 操作数由对象Object通过this指针隐含传递
//重载为友元函数:operator op(Object) 操作数由参数表的参数Object提供class Complex
{//前置++操作符 用全局函数实现friend Complex& operator++(Complex &c1);//后置++操作符 用全局函数实现friend Complex operator++(Complex &c1, int);
public:Complex(int a = 0, int b = 0){this->a = a;this->b = b;}void printCom(){cout << a << " + " << b << "i" << endl;}//~Complex()//{// cout << "析构函数" << endl;//}
public://前置-- 成员函数法 实现 --运算符重载Complex& operator--(){this->a--;this->b--;return *this;}//后置-- 成员函数法 实现 --运算符重载Complex operator--(int){Complex tmp = *this;//这里把*this的临时值拷贝给tmp//return *this;this->a--;this->b--;return tmp;}
private:int a;int b;
};
//前置++操作符 用全局函数实现
Complex& operator++(Complex &c1)
{c1.a++;c1.b++;return c1;
}
//后置++操作符 用全局函数实现
//先使用 在让c1加加
Complex operator++(Complex &c1, int)
{Complex tmp = c1;c1.a++;c1.b++;return tmp;
}
//C++中通过一个占位参数来区分前置运算和后置运算
void main()
{Complex c1(1, 2), c2(3, 4);//前置++操作符 用全局函数实现++c1;//operator++(c1);//Complex operator++(Complex &c1);c1.printCom();//前置--操作符 成员函数方法--c1;//c1.operator--();//Complex& operator--()c1.printCom();//后置++ 全局函数法c2++;c2.printCom();//operator++(c1);//Complex operator++(Complex &c1, int) //函数占位参数 和 前置++ 相区别//后置-- 成员函数法c2--;//c1.operator--(int);//Complex operator--(int);c2.printCom();cout << " << endl;system("pause");return;
}
全局函数、类成员函数方法实现运算符重载步骤
1)要承认操作符重载是一个函数,写出函数名称operator+ ()
2)根据操作数,写出函数参数
3)根据业务,完善函数返回值(看函数是返回引用 还是指针 元素),及实现函数业务
1)友元函数和成员函数选择方法
Ø 当无法修改左操作数的类时,使用全局函数进行重载
Ø =, [], ()和->操作符只能通过成员函数进行重载
2)用友元函数重载 << >>操作符
Ø istream 和 ostream 是 C++ 的预定义流类
Ø cin 是 istream 的对象,cout 是 ostream 的对象
Ø 运算符 << 由ostream 重载为插入操作,用于输出基本类型数据
Ø 运算符 >> 由 istream 重载为提取操作,用于输入基本类型数据
Ø 用友员函数重载 << 和 >> ,输出和输入用户自定义的数据类型
a)用全局函数方法实现 << 操作符
ostream& operator<<(ostream &out, Complex &c1)
{
//out<<"12345,生活真是苦"<<endl;
out<<c1.a<<"+ "<<c1.b<<"i "<<endl;
return out;
}
//调用方法
cout<<c1;
//链式编程支持
cout<<c1<<"abcc";
//cout.operator<<(c1).operator<<("abcd");
//函数返回值充当左值 需要返回一个引用
b)类成员函数方法无法实现 <<操作符重载
//因拿到cout这个类的源码
//cout.operator<<(c1);
#include <iostream>
using namespace std;class Complex
{friend ostream& operator<<(ostream &out, Complex &c1);
public:Complex(int a = 0, int b = 0){this->a = a;this->b = b;}void printCom(){cout << a << " + " << b << "i" << endl;}public:int a;int b;
};
/*
void operator<<(ostream &out, Complex &c1)
{
out<<"12345 生活真是苦"<<endl;
out<<c1.a << " + " << c1.b << "i" << endl;
}
*/
//全局函数法 重载<<
ostream& operator<<(ostream &out, Complex &c1)
{out << "12345 生活真是苦" << endl;out << c1.a << " + " << c1.b << "i" << endl;return out;
}
void main()
{int a = 10;Complex c1(1, 2), c2(3, 4);cout << a << endl; //按照数据类型 //1 用全局函数方法实现 << 操作符 //用友元函数:operator op(ObjectL,ObjectR) //ostream& operator<<(ostream &out, Complex &c1)cout << c1;//2 ostream 类中 添加 成员函数 .operator<<//ostream不是开源的,你无法在这个类里面添加成员函数//cout.operator<<(c1)//2 函数返回值当左值 需要返回一个引用cout << c1 << "aaddddd";//cout.operator<<(c1).operator<<("aaddddd");system("pause");}
3) 友元函数重载操作符使用注意点
a) 友员函数重载运算符常用于运算符的左右操作数类型不同的情况
)其他
Ø 在第一个参数需要隐式转换的情形下,使用友员函数重载运算符是正确的选择
Ø 友员函数没有 this 指针,所需操作数都必须在参数表显式声明,很容易实现类型的隐式转换
Ø C++中不能用友员函数重载的运算符有
= () [] ->
4 )友元函数案例vector类
#include <iostream> using namespace std;
//为vector类重载流插入运算符和提取运算符 class vector { public : vector( int size =1 ) ; ~vector() ; int & operator[]( int i ) ; friend ostream & operator << ( ostream & output , vector & ) ; friend istream & operator >> ( istream & input, vector & ) ; private : int * v ; int len ; };
vector::vector( int size ) { if (size <= 0 || size > 100 ) { cout << "The size of " << size << " is null !n" ; abort() ; } v = new int[ size ] ; len = size ; }
vector :: ~vector() { delete[] v ; len = 0 ; }
int &vector::operator[]( int i ) { if( i >=0 && i < len ) return v[ i ] ; cout << "The subscript " << i << " is outside !n" ; abort() ; } ostream & operator << ( ostream & output, vector & ary ) { for(int i = 0 ; i < ary.len ; i ++ ) output << ary[ i ] << " " ; output << endl ; return output ; } istream & operator >> ( istream & input, vector & ary ) { for( int i = 0 ; i < ary.len ; i ++ ) input >> ary[ i ] ; return input ; }
void main() { int k ; cout << "Input the length of vector A :n" ; cin >> k ; vector A( k ) ; cout << "Input the elements of vector A :n" ; cin >> A ; cout << "Output the elements of vector A :n" ; cout << A ; system("pause"); } |
C++编译器是如何支持操作符重载机制的?
Ø 赋值运算符重载用于对象数据的复制
Ø operator= 必须重载为成员函数
Ø 重载函数原型为:
类型 & 类名 :: operator= ( const 类名 & ) ;
案例:完善Name类,支持=号操作。
结论:
1//先释放旧的内存
2返回一个引用
3=操作符 从右向左
//obj3 = obj1; // C++编译器提供的 等号操作 也属 浅拷贝 // obj4 = obj3 = obj1 //obj3.operator=(obj1)
Name& operator=(Name &obj1) { //1 先释放obj3旧的内存 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赋值给obj3 strcpy(m_p, obj1.m_p); return *this; }
//重载=操作符 |
#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;//解决方案: 手工的编写拷贝构造函数 使用深copyName(const Name& obj){m_len = obj.m_len;m_p = (char *)malloc(m_len+1);//+1别忘记strcpy(m_p,obj.m_p);}//obj3 = obj1; // C++编译器提供的 等号操作 也属 浅拷贝//obj3.operator=(obj1)//void operator=(Name &obj)//{// //1 释放旧的内存// if (this->m_p!=NULL)// {// delete[] m_p;// m_len = 0;// }// //2 根据obj内存大小分配大小// this->m_len = obj.m_len;// this->m_p = new char[m_len + 1];// //3 把obj1拷贝过去// strcpy(m_p,obj.m_p);// //return *this;//返回当前对象//}Name& operator=(Name &obj){//1 释放旧的内存if (this->m_p != NULL){delete[] m_p;m_len = 0;}//2 根据obj内存大小分配大小this->m_len = obj.m_len;this->m_p = new char[m_len + 1];//3 把obj1拷贝过去strcpy(m_p, obj.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 Objmain()
{Name obj1("abcdefg");Name obj2 = obj1; //C++编译器提供的 默认的copy构造函数 浅拷贝 解决方法:重写拷贝构造函数Name obj3("obj3");obj3 = obj1; // C++编译器提供的 等号操作 也属 浅拷贝 用重写拷贝构造函数 已经解决不了这个问题 得采取重载=操作符//成员函数法//obj3.operator=(obj1);//Name& operator=(Name& obj1)obj1 = obj2 = obj3;// "="执行顺序是自右向左//先执行obj2 = obj3//obj2.operator=(obj3);//obj1 = void;不支持链式编程
}void main()
{Objmain();cout << " << endl;system("pause");return;
}
3重载数组下表运算符[]
重载[]和()运算符
Ø 运算符 [] 和 () 是二元运算符
Ø [] 和 () 只能用成员函数重载,不能用友元函数重载
重载下标运算符 []
[] 运算符用于访问数据对象的元素
重载格式 类型 类 ::operator[] ( 类型 ) ;
设 x 是类 X 的一个对象,则表达式
x [ y ]
可被解释为
x . operator [ ] ( y )
() 运算符用于函数调用
重载格式 类型 类 ::operator() ( 表达式表 ) ;
例1
设 x 是类 X 的一个对象,则表达式
x( arg1, arg2, … )
可被解释为
x. operator () (arg1, arg2, … )
案例:
//例2:用重载()运算符实现数学函数的抽象
#include <iostream>
class F
{public :
double operator( ) ( double x , double y ) ;
} ;
double F :: operator ( ) ( double x , double y )
{return x * x + y * y ; }
void main ( )
{
F f ;
cout << f (5.2 , 2.5 ) << endl ; //f . operator() (5.2, 2.5)
}
比较普通成员函数
//例3 用重载()运算符实现 pk 成员函数
#include <iostream.h>
class F
{public :
double memFun ( double x, double y ) ;
} ;
double F :: memFun ( double x, double y )
{ return x * x + y * y ; }
void main ( )
{
F f ;
cout << f.memFun( 5.2 , 2.5 ) << endl ;
}
#include <iostream>
using namespace std;class FF
{
public:int operator() (int a = 0, int b = 0){return a*a + b*b;}};class FF1
{
public:int MemFunc(int a = 0, int b = 0){return a*a + b*b;}
};void main()
{FF f;int c = f(2, 4);FF1 f1;c = f1.MemFunc(2,3);//operator() (int a, int b)cout << " << endl;system("pause");return;
}
理论知识:
1)&&和||是C++中非常特殊的操作符
2)&&和||内置实现了短路规则
3)操作符重载是靠函数重载来完成的
4)操作数作为函数参数传递
5)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 && (t1 + t2)){//t1&&t1.operator+(t2) //t1.operator&&(t1.operator+(t2)) //虽然可以执行,但是破坏了 短路规则 cout << "两个函数都被执行了,而且是线执行了+" << endl;}//2 && 运算符的结合性// 两个逻辑与运算符 在一块的时候, 才去谈 运算符的结合性// 从左到右 (t1 + t2) && t1 ; 运算结果 && t2)//if( (t1 + t2) && t1 && t2){//t1.operator+(t2) && t1;//(t1.operator+(t2)).operator&&(t1);cout << "两个函数都被执行了,而且是先执行了+" << endl;}system("pause");return;
}
读《C++编程规范》读到第30条:“避免重载&&、||或,(逗号)”,一直不能完全吃透。今天才理解,原来是这么回事:
内建的&&(逻辑与)、||(逻辑或)和,(逗号)操作符总是满足以下性质:
上面第2点和第3点又叫做“短路求值法(Short Circut Evaluation)”。
如果我们重载了这三个运算符,会发生什么呢?事实上,当一个操作符被重载时,编译器就会将该操作符视为一个函数,而不是一个真正的操作符。一个函数的参数总是会被全部求值,而且其求值顺序是未定义的。因此上述三个性质就全都不能满足了!
摘要:
明智就是知道合适适可而止:内置的&&,||和,(逗号)得到了编译器的特殊照顾。如果重载它们,它们就会变成普通函数,具有完全不同的语义(这将违反地26条:保持重载操作符的自然语义和第31条:不要编写依赖函数参数求值顺序的代码),这肯定会引入微妙的错误和缺陷。不要轻率地重载这些操作符。
不能重载operator&&、operator||或者operator,的主要原因是,无法在三种情况下实现内置操作符的完整语义,而程序员通常都会需要这些语义。说的更具体一些,就是内置版本的特殊之处在于它是从左到右求职,而且&&和||还使用短路求值。
内置版本的&&和||首先计算左边表达式的值,如果这个值能够决定结果,就不会计算第二个表达式的值,但是如果重载&&和||,这些函数在调用在执行执行总是对所有的参数进行求值,同时函数参数的求值顺序是不确定的。
逗号操作符也存在同样的脆弱性。与&&和||一样,内置逗号保证其表达式是从左到右按顺序求值的。用户自定义的逗号操作符无法保证是从左到右求值
#include <iostream>
#include <string>using namespace std;string retString()
{cout << "string" << endl;return "hello";
}int retInt()
{cout << "int" << endl;return 47;
}
//2
//用VS2010编译后,程序就会先输出string,再输出int,可见操作数是从右向左求值的。
//void operator , (int a, const string &b)
//{
//}
//3
void operator , (const int &a, const string &b)
{cout << a << b << endl;
}
//也就是把左操作数a的定义变成常量引用。那么程序又会先输出int再输出string了。你可能会怀疑此时程序调用的是内建逗号操作符,那么可以在重载定义中加一条cout输出语句,就知道调用的其实还是重载操作符。只不过把参数的定义改了改,参数的求值顺序就变了,是不是很“神奇”?
void main()
{retInt(), retString();//1 不重载,操作符时候 输出结果//int// string// 请按任意键继续. . .//2 //string// int// 请按任意键继续. . .//3//int// string// 47hello// 请按任意键继续. . .system("pause");
}
#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) ) cout<<"两个函数都被执行了,而且是先执行了+"<<endl; }
system("pause"); return ; } |
|
添加<< >>
#pragma once
#include <iostream>
using namespace std;
//Array.h
class Array
{friend ostream& operator<<(ostream& out, Array& a1);//重载输入流friend istream& operator>>(istream& input, Array& obj);
public:Array(int length);Array(const Array& obj);~Array();
public:void setData(int index,int value);int getData(int index);int length();
public:int& operator[](int index);//重载=操作符Array& operator=(Array& a1);//重载==bool operator==(Array& a2);//重载!=bool operator!=(Array& a2);
public:int m_length;int *m_space;
};/
Array.cpp
#include "Array.h"//int m_length;
//int *m_space;
Array::Array(int length)
{if (length < 0){length = 0;}this->m_length = length;this->m_space = new int[this->m_length +1];//int myarray.m_space[m_length+1]
}
//拷贝构造函数
Array::Array(const Array& obj)
{this->m_length = obj.m_length;//根据容量 分配相应大小的内存空间this->m_space = new int[this->m_length];for (int i = 0; i < this->m_length; i++){this->m_space[i] = obj.m_space[i];}
}//设置数组下标值
void Array::setData(int index, int value)
{this->m_space[index] = value;
}//获取数组下标对应数组值
int Array::getData(int index)
{return this->m_space[index];
}//获取数组长度
int Array::length()
{return this->m_length;
}
//重载=操作符
//a3 = a1;
Array& Array::operator=(Array& a1)
{//1 释放旧的内存if (this->m_space!= NULL){delete[] m_space;m_space = NULL;m_length = 0;}//2 根据a1分配内存大小m_length = a1.m_length;m_space = new int[m_length + 1];//3 拷贝数据for (int i = 0; i < m_length; i++){this->m_space[i] = a1[i];}return *this;
}
//析构函数
Array::~Array()
{if (this->m_space != NULL){delete[] this->m_space;this->m_space = NULL;this->m_length = 0;}
}
//1 重载[]
//a1.operator[index]
//函数返回值当左值,需要返回一个引用
//应该返回一个引用(元素本身) 而不是一个值
//int& operator[](int index)
int& Array::operator[](int index)
{return this->m_space[index];
}//重载==
bool Array::operator==(Array& a2)
{if (this->m_length != a2.m_length){return false;}for (int i = 0; i < m_length; i++){if (this->m_space[i] != a2.m_space[i]){return false;}}return true;
}//重载!=
bool Array::operator!=(Array& a2)
{return !(*this == a2);
}ostream& operator<<(ostream& out, Array& a1)
{for (int i = 0; i < a1.m_length; i++){out << a1.m_space[i] << " ";}return out;
}istream& operator>>(istream& input, Array& obj)
{for (int i = 0; i < obj.m_length; i++){input >> obj.m_space[i];}return input;
}//main.cpp
#include "Array.h"
#include <iostream>
using namespace std;int main()
{Array a1(10);for (int i = 0; i < a1.length(); i++){//a1.setData(i,i);//1 重载[] //a1.operator[index]//函数返回值当左值,需要返回一个引用//应该返回一个引用(元素本身) 而不是一个值//int& operator[](int index)a1[i] = i;}cout << "n打印数组a1:n";for (int i = 0; i < a1.length(); i++){//cout << a1.getData(i) << " ";cout << a1[i] <<" ";}cout << "n==================分界线=================n";Array a2 = a1;cout << "n打印数组a2:n";for (int i = 0; i < a2.length(); i++){cout << a2.getData(i) << " ";}cout << "n==================分界线1=================n";//重载=操作符//成员函数法 a3.operator=(a1);//Array& operator=(Array& a1);Array a3(10);a3 = a2;a3 = a2 = a1;for (int i = 0; i < a2.length(); i++){cout << a3[i] << " ";}cout << "n==================重载双等号=================n";//成员函数法 a1.operator==(a2);//bool operator==(Array& a2); if (a1 == a2){cout << "相等" << endl;}else{cout << "不等于" << endl;}cout << "n==================重载不双等号=================n";//成员函数法 a1.operator!=(a2);//bool operator!=(Array& a2);if (a1 != a2){cout << "不相等" << endl;}else{cout << "等于" << endl;}cout << "n==================重载输出流=================n";cout << a1;//ostream& operator<<(ostream& out, Array& a1);cout << "n==================重载输入流=================n";Array a4(5);cin >> a4;//友元函数//istream& operator>>(istream& input, Array& obj);cout << a4;cout << " << endl;system("pause");return 0;}
//MyString.h
#pragma once
#include <iostream>
using namespace std;class MyString
{//重载<< 操作符friend ostream& operator<<(ostream &out, MyString &s);//重载>> 操作符friend istream& operator>>(istream& input, MyString& obj);
public:MyString(int length = 0);//拷贝构造函数MyString(const MyString& obj);//有参构造函数MyString(const char *obj);~MyString();
public://重载=号操作符MyString& operator=(const char *p);MyString& operator=(const MyString &s);//重载[] 操作符char& operator[](int index);//重载==操作符bool operator==(const char *p) const;bool operator==(const MyString& obj) const;//重载!=操作符bool operator!=(const char *p) const;bool operator!=(const MyString& obj) const;//重载< int operator<(const char *p);int operator<(const MyString& obj);//重载>int operator>(const char *p);int operator>(const MyString& obj);//把类的指针属性 给漏出来
public:int length(){return m_length;}char *c_str(){return m_space;}const char *c_str2(){return m_space;}
private:int m_length;char *m_space;
};//MyString.cpp
#define _CRT_SECURE_NO_WARNINGS
#include "MyString.h"MyString::MyString(int length)
{if (length < 0){length = 0;m_space = new char[m_length + 1];strcpy(m_space,"");}else{m_length = length;m_space = new char[m_length + 1];memset(m_space,0,m_length); }}
//拷贝构造函数
//MyString s3 = s2;
MyString::MyString(const MyString& obj)
{m_length = obj.m_length;m_space = new char[m_length+1];strcpy(m_space,obj.m_space);
}//有参构造函数
MyString::MyString(const char *obj)
{if (NULL == obj){m_length = 0;m_space = new char[m_length+1];strcpy(m_space,"");}else{m_length = strlen(obj);//1. strlen 是函数,sizeof 是运算符//2. strlen 测量的是字符的实际长度,以'