1. 类对象的初始化
1.1 用构造函数实现数据成员的初始化
c++提供构造函数来处理对象的初始化。
构造函数是一种特殊的成员函数,不需要用户来调用它,而是在建立对象时自动执行。
构造函数的名字必须与类名相同。
1.1.1.不带参数的构造函数
又名 默认构造函数
一个类只能有一个默认构造函数,若用户没有定义构造函数,则系统会自动提供一个函数体为空的默认构造函数,不起初始化作用。如果用户希望在创建对象时就能使数据成员有初值,就得自己定义构造函数。
在建立对象的时候执行构造函数,给数据赋初值。如果定义了多个对象,每个对象的数据的初值都是相同的。
补充1:这里需要强调一点,如果我们不写构造函数,则编译器会默认生成构造函数,此时编译器会做一个偏心的处理:内置类型(eg:int,float,douyble…)不会初始化,而自定义类型会调用其无参构造函数,这里算是一个C++的设计缺陷,当然,从C++11开始,在类中定义类成员时可以顺便初始化,算是一定程度上补上了这个缺陷。
补充2:
- 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只有一个。
- 无参构造函数,全缺省构造函数,以及我们没写是编译器自动生成的构造函数,都可以认为是默认构造函数。
public:
Time()
{
hour=0;
minute=0;
sec=0;
}
1.1. 2.带参数的构造函数
用带参的构造函数,可以使同类的不同对象中的数据具有不同的初值
1.在类中定义构造函数
2.在定义对象时指定实参
1.在类中定义构造函数
Box(int h,int w,int len)
{
height=h;
width=w;
length=len;
}
2.在定义对象时指定实参
Box box1(12,25,30);
1.1.3.初始化列表
在构造函数时用参数初始化表实现对数据的赋值
Box(int h,int w,int len):height(h),width(w),lengh(len){}
使用初始化列表是能够提高我们的初始化效率的,原因在于就算我们不显式写出初始化列表,编译器也会自动产生默认初始化列表进行初始化,我们可以认为初始化列表是类内成员的定义位置,默认初始化列表定义但是不初始化。
- 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
- 类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用成员变量
- const成员变量
- 自定义类型成员(该类没有默认构造函数
1.1.4.带默认参数的构造函数
在某些情况下,在定义构造函数时可以加入默认参数,这也叫做缺省参数
Box(int h=10,int w=10,int len=10)
{
height=h;
width=w;
length=len;
}
1.1.5 拷贝构造函数
在创建对象时,可否创建一个与一个对象一某一样的新对象呢?
构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
拷贝构造函数也是特殊的成员函数,其特征如下:
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
如果我们不显式定义拷贝构造,则编译器会默认生成拷贝构造,与构造和析构不同的是,这里不会去区分内置类型和自定义类型成员,编译器都会处理。
- 内置类型,基于字节序的浅拷贝
- 自定义类型,调用其内拷贝构造完成拷贝
那么编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,我们还需要自己实现吗?
我们可以看下面的一段代码:
此时代码会出现崩溃,原因在于默认拷贝使用浅拷贝使得s1._str和 s2._str指向了同一块空间,当s2先析构时,释放此块空间,s1后析构,再次尝试对此块空间析构,造成一块空间的二次释放,显然错误。
因此,在这类场景下,我们需要自己实现深拷贝构造函数。
class String
{
public:
String(const char* str = "jack")
{
_str = (char*)malloc(strlen(str) + 1);
strcpy(_str, str);
}
~String()
{
cout << "~String()" << endl;
free(_str);
}
private:
char* _str;
};
int main()
{
String s1("hello");
String s2(s1);
}
1.2构造函数的重载
使用场景:在一个类中可以定义多个构造函数,供用户选择。
这些构造函数具有相同的名字,但参数的个数或参数的类型不同,这叫做构造函数的重载
#include<iostream>
using namespace std;
class Box
{
public:
Box()
{
height = 10;
width = 10;
lengh = 10;
};
Box(int h, int w, int len) :height(h), width(w), lengh(len) {}
int volume();
private:
int height;
int width;
int lengh;
};
int Box::volume()
{
return(height * width * lengh);
}
int main()
{
Box box1,box2(15,30,25);
cout << "box1 volume:" << box1.volume() << endl;
cout << "box2 volume:" << box2.volume() << endl;
return 0;
}
对于上面的程序,值得一提的是,我们建立的两个构造函数同名,那么系统如何辨别调用哪一个函数?是根据函数调用的形式去确定对应哪一个构造函数。
2. 类对象作类成员
应用背景:有时候定义类成员时,可能需要某成员为一个类,方便进行使用
在这里我们以person类为例子,它包含两个类对象,姓名与手机,其中手机包括两个属性:品牌和价格。为了体现明确的逻辑包含关系,我们将手机定义为类对象。
#include<iostream>
#include<string>
using namespace std;
//类对象作为类成员
//手机类
class Phone
{
public:
Phone(string pName,int pp)
{
m_PName = pName;
Price=pp;
}
//手机品牌名称
string m_PName;
int Price;
};
//大类
class Person
{
public:
//Phone m_Phone=pName 隐式转换法
Person(string name,string pName)
:m_Name(name),m_Phone(pName){}
//姓名
string m_Name;
//手机
Phone m_Phone;
};
//当其他类作为本类成员时,构造函数先构造类对象,再构造自身
//析构的顺序与构造相反
void test01()
{
Person p("Mike","iphone");
cout << p.m_Name << "拿着" << p.m_Phone.m_PName << endl;
}
int main()
{
test01();
return 0;
}
值得注意的是,这个程序有两个类,那么系统先创建了那个类呢?
其实,当其他类作为本类成员时,构造函数先构造类对象,再构造自己,也就是说,先建立了手机类,再建立了大类。
而析构的顺序与构造相反。
3. 静态成员函数
static void func()//书写格式
静态函数的要点:
1.所有对象共享一个函数
2.静态成员函数只能访问静态成员变量
3.静态成员函数也存在访问权限
#include<iostream>
using namespace std;
//静态成员函数
//所有对象共享一个函数
//静态成员函数只能访问静态成员变量
class Person
{
public:
static void func()
{
m_A = 100;//静态成员函数 可以访问 静态成员变量
~~m_B = 200;~~ //报错,静态成员函数 不可以访问
// 非静态成员变量,无法区分到底是哪个对象的数据
cout << "static void func调用" << endl;
}
static int m_A;//静态成员变量
int m_B;//非静态成员变量
//静态成员函数也有访问权限
private:
static void func2()
{
cout << "static void func2调用" << endl;
}
};
int Person::m_A=0;//静态成员变量要在类外声明。
//两种访问方式
void test()
{
//通过对象访问
Person p;
p.func();
//通过类名创建
Person::func();
//Person::func2();//报错,在类外无法访问私有;
}
int main()
{
return 0;
}
对于静态成员函数,它其实是类的一部分而不是对象的一部分。所以用类或对象都可以创建以及访问。
同时,静态成员n函数也存在着访问权限,它在类外无法访问私有成员
4. 对象指针
1.1指向对象的指针
书写格式: 类名 * 对象指针名
Time *pt;
Time t1;
pt=&t1;
1.2指向对象成员的指针
1.2.1指向对象数据成员的指针
书写格式: 数据类型名 *指针变量名
int*pi;
p1=&t1.hour;//将对象t1的数据成员hour的地址赋给p1
cout<<*p1<<endl;
1.2.2指向对象成员函数的指针
1.指向普通函数的指针变量:
书写格式: 类型名(*指针变量名)(参数表列)
void(*p)(); //p是void型函数的指针变量
p=fun; //将fun函数的入口地址赋给p,p指向函数fun;
(*p)(); //调用fun函数
2.指向对象成员函数的指针变量:
书写格式:类型名(类名:: *指针变量名)(参数表列)
void (Time:: *p2)();
p2=&Time::get_time;
1.3指向当前对象的this指针
每一个成员函数都包含一个特殊的指针,这个指针的名字是固定的,称为this.它是指向苯类对象的指针,他的值是当前被调用的成员函数所在的对象的起始地址
this指针是隐式使用的,一般不用写。
1.4 空指针访问成员函数
#include<iostream>
using namespace std;
class Person
{
public:
void showClassName()
{
cout << "this is Person class" << endl;
}
void showPersonAge()
{
cout << "age="<<m_Age << endl;//默认加了this->m_Age,
//报错原因是传入的指针是为空
}
int m_Age;
};
void test01()
{
Person* p = NULL;
p->showClassName();
p->showPersonAge();
}
int main()
{
void test01();
return 0;
}
观察该程序,并运行发现报错(部分编译器不会报错),原因在于showPersonName函数。它使用了m_Age这个成员,系统默认在它前面加上了一个this指针,即this->m_Age.
但我们给p指针定义为空,也就是说未创建一个实际存在的对象,this指针找不到类,自然也找不到其中的成员了。
如何解决?
cpp
void showPersonAge()
{
if (this == NULL)//加上判空条件
{
return;
}
cout << "age="<<m_Age << endl;
}
5. 公用数据的保护
1.1定义常对象
书写格式(两种):
类名 const 对象名 [ (实参表) ]
const 类名 对象名 [ (实参表) ]
说明:
1.定义常对象时,必须同时对之进行初始化,之后不可以改变
2.常对象只能调用它的常成员函数,不能调用该对象的普通成员函数
1.2定义常成员
1.2.1常数据成员
书写格式:
const 类名 成员名
说明:
1.常成员的值不可以改变
2.只能通过构造函数的参数初始化表对常数据成员进行初始化,任何其他函数不能对常数据成员赋值。
1.2.2常成员函数
常函数:
- 成员函数后 加const后我们称这个函数为常函数
- 常函数不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中仍可以修改。
常对象
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
我们先讲常函数:
书写格式:
void showPerson() const
{
}
在这里,我们要先了解一下this指针的本质,它其实是指针常量,它的指向是不可改变的。可以理解为Personconst this,此时该this指针只能指向Person类,并只能访问Person中的成员,但是要注意成员的值还是可以改变的喔。
倘若还想不让改变值,那么可以理解为const Personconst this,写成程序的话,则应该在成员函数名加const,即void showPerson() const。这里的const实际上修饰的是this 指针,让指针指向的值也变得不可修改
值得一提的是,c++还提供了一种方法,使变量即使在常函数或常对象中也可以修改数值。只要在定义变量时在类型前加关键字mutable。即 mutable int i;
#include<iostream>
using namespace std;
class Person
{
public:
//this指针的本质:指针常量 指针指向不可修改
//Person *const this
//const Person *const this
void showPerson()const
{
//m_Age = 100;//this->m_Age=100;
this = NULL;
}
int m_Age;
};
void test01()
{
Person p;
p.showPerson();
}
int main()
{
void test01();
return 0;
}
1.3指向对象的常指针
常指针 是 不能改变指向的指针变量
书写格式:类名 * const 指针变量名
说明:
指向对象的常指针变量的值不能改变,但可以改变其所指对象的值
1.4指向常对象的指针变量
书写格式:
const 类名 * 指针变量名
说明:
1.如果一个变量已经被声明为常变量,只能用指向常变量的指针变量指向它,而不能用一般的指针变量指向它
2.指向常变量的指针变量除了可以指向常变量,还可以指向未被声明为const的变量,此时不能通过指针变量改变该变量的值。
1.5对象的常引用
6. const修饰程序函数
讲完常函数,我们来研究一下常对象
书写方式:
const Person p;
我们都知道常对象中的成员值不可改变,但就像我之前提到的,倘若你在类内定义变量时使用了mutable关键字,那么该变量不论在常函数还是常对象中使用时都可以被改变值。
同时,常对象只能调用常函数。原因通常普通函数函数会修改属性。
7. 友元
- 全局函数做友元
- 类做友元
- 成员做友元
1.全局函数做友元
#include<iostream>
using namespace std;
#include<string>
//建筑物类
class Building
{
//申明为友元函数
friend void goodGay(Building* building);
public:
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
string m_SittingRoom;//客厅
private:
string m_BedRoom;//卧室
};
void goodGay(Building *building)
{
cout<<"好基友全局函数 正在访问:"<<building->m_SittingRoom<<endl;
cout << "好基友全局函数 正在访问:" << building->m_BedRoom<< endl;
}
void tset01()
{
Building building;
goodGay(&building);
}
int main()
{
void test01();
system("pause");
return 0;
}
2.类做友元
#include<iostream>
using namespace std;
#include<string>
class Building;
class GoodGay
{
public:
GoodGay();
void visit();//参观函数,访问Building 中的公共属性
private:
Building* building;
};
class Building
{
**friend class GoodGay;**
public:
Building();
string m_SittingRoom;//客厅
private:
string m_BedRoom;//卧室
};
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay::GoodGay()
{
building = new Building;
}
void GoodGay::visit()
{
cout << "好基友全局函数 正在访问:" << building->m_SittingRoom << endl;
cout << "好基友全局函数 正在访问:" << building->m_BedRoom << endl;
}
void tset01()
{
GoodGay gg;
gg.visit();
}
int main()
{
void test01();
//system("pause");
return 0;
}
3.成员函数做友元
#include<iostream>
using namespace std;
#include<string>
class Building;
class GoodGay
{
public:
GoodGay();
void visit();//参观函数,访问Building 中的公共属性
void visit2();//让visit2函数不可以访问Building中的私有成员
private:
Building* building;
};
class Building
{
friend void GoodGay::visit();
public:
Building();
string m_SittingRoom;//客厅
private:
string m_BedRoom;//卧室
};
//类外实现成员函数
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay::GoodGay()
{
building = new Building;
}
void GoodGay::visit()
{
cout << "好基友全局函数 正在访问:" << building->m_SittingRoom << endl;
cout << "好基友全局函数 正在访问:" << building->m_BedRoom << endl;
}
void GoodGay::visit2()
{
cout << "好基友全局函数 正在访问:" << building->m_SittingRoom << endl;
//cout << "好基友全局函数 正在访问:" << building->m_BedRoom << endl;
}
void tset01()
{
GoodGay gg;
gg.visit();
gg.visit2();
}
int main()
{
void test01();
//system("pause");
return 0;
}