提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
C++基础学习三
一,构造
构造函数和析构函数这两个函数是被编译器自动调用的。
无论你是否喜欢,对象的初始化和清理工作都是编译器强制我们要做的事情,即使你不提供初始化操作和清理操作,编译器也会给你增加默认的操作,只是这个默认初始化操作不会做任何事情,
class Person
{
public:
//构造函数写法
//与类名相同,没有返回值,不写void,可以发生重载(可以有参数),编译器自动调用,且只调用一次
Person()
{
}
//析构函数写法
//与类名相同,类名前面加一个~,没有返回值,不写void,不可以发生重载,自动调用,且一次
~Person()
{
}
}
1 构造函数的分类(构造和析构必须在public下才可以调用)
按照参数进行分类,无参构造函数,有参构造函数
按照类型进行分类, 普通构造函数,拷贝构造函数
class Person
{
public:
//构造函数写法
//与类名相同,没有返回值,不写void,可以发生重载(可以有参数),编译器自动调用,且只调用一次
Person()
{
cout<<"默认构造"<<endl;
}
Person(int a)
{
cout<<"有参构造"<<endl;
}
Person(const Person &p)//不能修改被拷贝的值
{
cout<<"有参构造"<<endl;
}
//析构函数写法
//与类名相同,类名前面加一个~,没有返回值,不写void,不可以发生重载,自动调用,且一次
~Person()
{
}
}
Person p1(1); //有参
Person p2(p1); //拷贝
Person p3; //默认构造函数不要加()
Person p3(); //编译器认为这是函数声明,返回值为Person,函数名为p3的函数声明
Person p4 = Person(100); //显示调用有参
Person p5 = person(p4);
Person(100) //叫匿名对象,匿名对象特点,如果编译器发现对象是匿名的,那么在这行代码的结束胡就释放这个对象
//不能用拷贝构造函数初始化匿名对象
Person(p5)//如果写成左值,编译器认为写成Person p5,与上面的p5重定义。如果写成Person p6 = Person(p5)可以
Person p7 = 100; //相当于Person p7 = Person(100),隐式类型装换
Person p8 = p7;
2 拷贝构造函数调用时机
1.用一个已经创建好 的对象初始化新的对象
2.以值传递的方式给函数参数传值
例如
void func(Person p) //Person p = Person(p1) 所以建议使用引用,节省开销
{
}
Person p1;
func(p1);
3.以值的方式返回局部对象
Person func2() //不能返回局部对象的引用,可以以值方式返回局部对象
{
Person p1;
return p1;
}
Person p = func(); //debug模式下会输出默认和拷贝两个构造
/*release版本只会出现一次默认构造,编译器将代码修改如下
Person p;不调用默认构造
func2(p);
void func2(Person &p)
{
Person p1 //调用默认构造
}
*/
3 构造函数调用规则
1.系统会默认给一个类提供3个函数,默认构造 ,拷贝构造、析构函数
2.当我们提供了有参的构造函数,那么系统就会再提供默认构造函数了,但是系统还是会提供默认拷贝构造函数
3.当我们提供了拷贝构造,系统就不会提供其他构造了
4 深拷贝和浅拷贝
如果属性里有指向堆区空间的数据,那么简单的浅拷贝会导致重复释放内存的异常
class Person
{
public:
Person()
{}
//初始化属性
Person(char * name,int age)
{
m_Name = (char*)malloc(strlen(name) + 1);
strcpy(m_Name, name);
m_age = age;
}
//拷贝构造 系统会提供默认拷贝构造,而且是简单的值拷贝
//自己提供拷贝构造,原因简单的浅拷贝会释放堆区空间两次,导致挂掉
//深拷贝
Person(const Person&p)
{
m_age = p.m_age;
m_Name = (char*)malloc(strlen(p.m_Name) + 1);
strcpy(m_Name, p.m_Name);
}
~Person()
{
cout << "析构函数调用" << endl;
if (m_Name!=NULL)
{
free(m_Name);
m_Name = NULL;
}
}
//姓名
char * m_Name;
//年龄
int m_age;
};
void test01()
{
Person p1("敌法",10);
Person p2(p1); //调用拷贝构造
}
5 初始化列表
class Person
{
public:
//有参构造初始化数据
/*Person( int a,int b,int c)
{
m_A = a;
m_B = b;
m_C = c;
}*/
Person() :m_A(10), m_B(20), m_C(30)
{}
//利用初始化列表 初始化数据
// 构造函数后面 + : 属性(参数), 属性(参数)...
Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c)
{}
int m_A;
int m_B;
int m_C;
};
void test01()
{
Person p1(10, 20, 30);
cout << "p1的m_A :" << p1.m_A << endl;
cout << "p1的m_B :" << p1.m_B << endl;
cout << "p1的m_C :" << p1.m_C << endl;
Person p2;
cout << "p2的m_A :" << p2.m_A << endl;
cout << "p2的m_B :" << p2.m_B << endl;
cout << "p2的m_C :" << p2.m_C << endl;
}
6 类对象作为类成员
类对象作为类成员时,构造顺序先将类对象一一构造,然后构造自己,析构的顺序是相反的
class Phone
{
public:
Phone()
{
cout << "手机的默认构造函数调用" << endl;
}
Phone(string name)
{
cout << "手机的有参构造调用" << endl;
m_PhoneName = name;
}
~Phone()
{
cout << "手机的析构函数调用" << endl;
}
string m_PhoneName;
};
class Game
{
public:
Game()
{
cout << "Game的默认构造函数调用" << endl;
}
Game(string name)
{
cout << "Game的有参构造调用" << endl;
m_GameName = name;
}
~Game()
{
cout << "Game的析构函数调用" << endl;
}
string m_GameName;
};
class Person
{
public:
Person()
{
cout << "Person的默认构造函数调用" << endl;
}
Person(string name, string phoneName, string gameName) : m_Name(name), m_Phone(phoneName), m_Game(gameName)
{
cout << "Person的有参构造调用" << endl;
//m_Name = name;
}
void playGame()
{
cout << m_Name << " 拿着《" << m_Phone.m_PhoneName << "》牌手机 ,玩着《" << m_Game.m_GameName << "》游戏" << endl;
}
~Person()
{
cout << "Person的析构函数调用" << endl;
}
string m_Name; //姓名
Phone m_Phone; //手机
Game m_Game; //游戏
};
//类对象作为类成员时候,构造顺序先将类对象一一构造,然后构造自己, 析构的顺序是相反的
void test01()
{
Person p("狗蛋","苹果","切水果");
p.playGame();
}
7 explicit关键字
class MyString
{
public:
MyString(const char * str)
{
//
}
explicit MyString(int a)
{
mSize = a;
}
char* mStr;
int mSize;
};
void test01()
{
MyString str = "abc";
MyString str2(10);
//MyString str3 = 10; //做什么用图? str2字符串为 "10" 字符串的长度10
//隐式类型转换 Mystring str3 = Mystring (10);
// explicit关键字 ,防止隐式类型转换
}
二、new
malloc申请的内存可能会失败,且申请后需要初始化。
所有new出来的对象都会返回该类型的指针,mallo返回void *
new和delete是运算符,malloc是函数,配合free使用
通过new创建数组,一定为会调用默认构造,所以一定要有默认构造
class Person
{
public:
Person()
{
cout << "默认构造调用" << endl;
}
Person(int a)
{
cout << "有参构造调用" << endl;
}
~Person()
{
cout << "析构函数调用" << endl;
}
};
void test01()
{
//Person p1; 栈区开辟
Person * p2 = new Person; //堆区开辟
//所有new出来的对象 都会返回该类型的指针
//malloc 返回 void* 还要强转
//malloc会调用构造吗? 不会 new会调用构造
// new 运算符 malloc 函数
//释放 堆区空间
// delete也是运算符 配合 new用 malloc 配合 free用
delete p2;
}
void test02()
{
void *p = new Person(10);
//当用void* 接受new出来的指针 ,会出现释放的问题
delete p;
//无法释放p ,所以避免这种写法
}
void test03()
{
//通过new开辟数组 一定会调用默认构造函数,所以一定要提供默认构造
Person * pArray = new Person[10];
//Person pArray2[2] = { Person(1), Person(2) }; //在栈上开辟数组,可以指定有参构造
//释放数组 delete [] 编译器会给堆上的数组一个数组大小的记录,如果不加[],delete就不会去找这个记录
delete [] pArray;
}