端午节就复习一下C++基础吧。
目录
1.1 类的定义
C++语言中,类和结构体类似,其中可以定义数据和方法,提供 class关键字定义类。
类的定义包括两部分,类头和类体。类头由 class关键字和类名组成,类体由一组大括号 “{}”和一个“;”组成类体中通常定义类的数据和方法,其中数据描述的是类的特征(也称之为属性)。方法实际上为类内定义的函数,描述的类的行为。
class CUser
{
char m_Username[128];
char m_Password[128];
bool Login()
{
if(strcmp(m_Username,"MR") == 0 && strcmp(m_Password,"KJ") == 0 )
{
printf("登录成功!\n");
return true;
}
else
{
printf("登录失败!\n");
return false;
}
}
};
·上述代码定义了一个CUser类,其中包含有两个数据成员和一个方法。对于方法的定义,也可以放在类外进行定义,方法名需要使用类名和限域限定符 " :: " ,依次来标记该方法属于哪一个类的方法。
class CUser
{
char m_Username[128];
char m_Password[128];
bool Login()
};
bool CUser::Login()
{
if(strcmp(m_Username,"MR") == 0 && strcmp(m_Password,"KJ") == 0 )
{
printf("登录成功!\n");
return true;
}
else
{
printf("登录失败!\n");
return false;
}
}
注意:不可以对类内的数据成员进行初始化操作!
1.2 类成员的访问
类成员主要是指类中的数据成员和方法,在定义类时,类成员是具有访问限制,C++提供有三种限定符来标识类成员的访问,分别为 pubilc、protected和private。
public成员称之为公共成员,该成员可以在程序中的任何地方进行访问。
protected成员称之为保护成员,该成员只能在该类和该类的派生类(子类)中进行访问,除此之外,程序的其他地方没有访问权限。
private成员称之为私有成员,该成员只能在该类中使用,派生类和其他地方没有访问权限。
在定义类时,没有设置访问限定符时,默认为private。
class CUser
{
private:
char m_Username[128];
char m_Password[128];
public:
void SetUsername(const char* pUsername)
{
if(pUsername != Null)
{
strcpy(m_Username,pUsername)
}
}
char* GetUsername()const
{
return (char*)m_Username;
}
void SetPassword(const char* pPassword)
{
if(pPassword!= Null)
{
strcpy(m_pPassword,pPassword)
}
}
char* GetpPassword()const
{
return (char*)m_pPassword;
}
bool Login()
{
if(strcmp(m_Username,"MR") == 0 && strcmp(m_Password,"KJ") == 0 )
{
printf("登录成功!\n");
return true;
}
else
{
printf("登录失败!\n");
return false;
}
}
};
在定义类之后,需要访问类内成员时,通常类成员的防问是通过对象来实现的,对象被称之为类的实例化。当程序中定义一个类时,并没有为其分配存储空间,只有当定义类的对象时,菜分配存储空间。对象的定义和普通变量的定义是一样的。
在定义类的对象之后,就可以访问类的成员了。
// 访问类成员通过对象的形式访问
CUser user;
//
user.SetUername("MR");
user.setPassword("KJ");
user.login();
在定义类对象时,也可以将类对象声明声明为一个指针。在程序中使用new运算符来分配指针内存。
// 访问类成员通过声明一个指针的形式访问
CUser *puser = new CUser;
// 或者如下也是合理
CUser *puser2 = new CUser();
如果类的对象被定义为指针时,需要使用 ->运算符来访问类成员。需要注意的是,如果类对象定义为常量指针,则对象只允许调用const方法。
// 访问类成员通过声明一个指针的形式访问
CUser *puser = new CUser;
user->SetUername("MR");
user->setPassword("KJ");
user->login();
delete pUser; // 记得要释放
1.3 构造函数和析构函数
每个类都有构造函数和析构函数。其中 ,构造函数在定义对象时被调用,析构函数在对象释放时被调用。如果用户没有提供构造函数和析构函数时,系统讲提供默认的构造函数和析构函数。
构造函数:该函数是一个与类同名的方法,可以没有参数,也可以有一个或者多个参数。但是构造函数没有返回值。如果构造函数没有参数,该函数称之为类的默认构造函数。
class CUser
{
private:
char m_Username[128];
char m_Password[128];
public:
CUser() // 显式定义一个默认的构造函数
{
strcpy(m_Username,"MR") // 在该函数内部给值进行赋值
strcpy(m_Password,"kj")
}
char* GetUsername()const
{
return (char*)m_Username;
}
char* GetpPassword()const
{
return (char*)m_pPassword;
}
};
int main(int argc , char* argvx [])
{
CUser user;
printf("%s\n",user.GetpPassword()); // 会调用构造函数内部的值进行输出
return 0;
}
说明: 一个类可以包含有多个构造函数,各个构造函数之间通过参数列表进行区分和调用。不再展开。
前面说到,我们不可以在类体内直接对数据成员进行初始化赋值。那如果在类中包含有常量或引用类型的数据成员时,该如何初始化呢??
类的构造函数通过使用 “ :: ” 运算符提供了初始化成员的方法。
class CBook
{
public:
char m_BookName[128];
const unsigned int m_price;
int m_ChapterNum;
CBook() // 在构造函数旁边使用 :: 运算符来对数据成员进行初始化
:: m_price(15),m_ChapterNum(12)
{
strcpy(m_BookName,"大学英语")
}
};
析构函数: 该函数在对象超出作用范围或使用delete运算符释放对象时被调用,用于释放对象占有的空间。与构造函数一样,如果用户没有显示提供析构函数,系统会提供默认的析构函数。析构函数也时以类名作为函数名,与构造函数的区别就是需要在函数名前加一个 “ ~ ”。
析构函数没有参数,因此不能重载。即一个类只会有一个析构函数。
1.4 类的继承
继承是面向对象的主要特征之一,此外还有封装和多态。它使得一个类可以从现有的类中派生,而不用重新定义一个新类。
例如一个员工类,其中包含有员工ID、姓名是、所属部门等信息。在定义一个操作员类,操作员也是属于公司的员工,因此该类中也会有员工类中的所有信息,还包含有密码信息、登录方法等新的属性。那么如果之前定义了员工类,咋在定义操作员类时可以从员工类派生出一个新的员工类,然后向该类中添加密码、登录方法等信息。
#define MAXLEN 128
class CEmployee
{
public:
int m_ID;
char m_Name[MAXLEN];
char m_Depart[MAXLEN];
CEmployee()
{
memset(m_name,0,MAXLEN);
memset(m_Depart,0,MAXLEN);
printf("员工类构造函数被调用\n");
}
void outputName()
{
printf("员工姓名: %s\n",m_Name);
}
};
class COpreate : public CEmployee
{
public :
char m_Password[MAXLEN];
bool Login()
{
if(strcmp(m_Username,"MR") == 0 && strcmp(m_Password,"KJ") == 0 )
{
printf("登录成功!\n");
return true;
}
else
{
printf("登录失败!\n");
return false;
}
}
};
上述代码在定义 COpreate类时使用了 “ : ”运算符。表示该类派生与一个基类;public关键字表示派生的类型为公有性;其后的CEmployee表示COpreate的基类,也就是父类。这样 COpreate类将会继承 CEmployee 类中所有的非私有(public和protected)成员(private成员不能被继承)。
当一个类从另一个类继承时,可以有3种派生类型,public、protected和private三种。
派生类型为公有型时,基类中的public数据成员和方法在派生类中任然是public。基类中的protected在派生类中同样也是protected。即不会改变基类在派生类中的访问权限。
派生类为保护型时,基类中的public和protected数据成员和方法在派生类中均为protected。
派生类型为私有型时,基类中的public和protected数据成员和方法在派生类中均为private。
int main(int argc , char* argvx [])
{
COpreate optr; // 定义一个访问COperator类对象
strcpy(optr.m_Name,"MR"); // 访问基类的 m_Name 成员
strcpy(optr.m_Password,"KJ"); // 访问 COpreate 中 m_Password 成员
optr.login(); // 访问 COpreate 中 login 方法
optr.OutputName(); // 访问基类中的 OutputName()的方法。
return 0;
}
1.4.1 实例化对象调用
当用户在父类中派生子类时,可能存在一种情况,即在父类中定义了一个与父类的方法同名的方法函数。这种情况,该子类的方法会隐藏父类的方法。
class COpreate : public CEmployee
{
public :
char m_Password[MAXLEN];
void outputName() // 父类 CEmployee 中也有该方法,此时会隐藏父类的方法
{
printf("操作员姓名: %s\n",m_Name);
}
bool Login()
{
if(strcmp(m_Username,"MR") == 0 && strcmp(m_Password,"KJ") == 0 )
{
printf("登录成功!\n");
return true;
}
else
{
printf("登录失败!\n");
return false;
}
}
};
int main(int argc , char* argvx [])
{
COpreate optr; // 定义一个访问COperator类对象
strcpy(optr.m_Name,"MR"); // 访问基类的 m_Name 成员
optr.OutputName(); // 会访问COpreate 中的 OutputName()的方法。
return 0;
}
上述代码会输出 :操作员姓名 :MR 。会调用 COpreate 类中的outputName方法,而不是基类中的outputName方法。
如果用户向调用基类中的outoutName()方法,需要加一个显示调用。
int main(int argc , char* argvx [])
{
COpreate optr; // 定义一个访问COperator类对象
strcpy(optr.m_Name,"MR"); // 访问基类的 m_Name 成员
optr.OutputName(); // 会访问 COpreate 中的 OutputName()的方法。
optr.CEmployee::OutputName(); // 会访问 CEmployee中的 OutputName()的方法。
return 0;
}
如果子类隐藏了父类的方法,那么父类中所有的同名的方法(重载方法),均会被隐藏!!
1.4.2 指针类型调用:
在派生一个子类后,也可以运用一个父类的类型指针,通过对子类的构造函数为其创建对象。
CEmployee *pWorker = new COpreate (); // 定义CEmployee 基类指针,调用子类的构造函数
如果此时,使用 pWorker对象去调用outputName方法,如果执行 pWorker ->outputName();那此时是执行 基类中outputName()方法还是子类中的outputName()方法呢?
答案是会执行基类 CEmployee 中的 outputName()方法。
编译器对 outputName()方法进行的是静态绑定,即根据对象定义时的类型来确定调用的是那个类的方法,由于 pWorker 指针是 CEmployee 类型,所有会调用基类 CEmployee的方法。
那如果我需要调用子类 COpreate 中的 outputName()方法需要怎么实现呢?通过定义虚方法可以实现这一点。
在定义方法(成员函数)时,在方法的前面使用virtual关键字,该方法即为虚方法,使用虚方法可以实现类的动态绑定,即根据对象运行时的类型来确定调用那个类的方法,而不是根据对象定义时的类型来确定调用那个方法。
#define MAXLEN 128
class CEmployee
{
public:
int m_ID;
char m_Name[MAXLEN];
char m_Depart[MAXLEN];
CEmployee()
{
memset(m_Name, 0, MAXLEN);
memset(m_Depart, 0, MAXLEN);
printf("员工类构造函数被调用\n");
}
virtual void outputName() // 虚函数
{
printf("员工姓名: %s\n", m_Name);
}
};
class COpreate : public CEmployee
{
public:
char m_Password[MAXLEN];
void outputName() // 父类 CEmployee 中也有该方法,此时会隐藏父类的方法
{
printf("操作员姓名: %s\n", m_Name);
}
bool Login()
{
if (strcmp(m_Name, "MR") == 0 && strcmp(m_Password, "KJ") == 0)
{
printf("登录成功!\n");
return true;
}
else
{
printf("登录失败!\n");
return false;
}
}
};
int main()
{
CEmployee *pWorker = new COpreate();
strcpy_s(pWorker->m_Name, "MR");
pWorker->outputName();
delete pWorker;
return 0;
}
上述代码既可实现,父类指针实现派生类的方法。
1.4.3 抽象类
在C++语言中,除了能够定义虚方法外,也有定义纯虚方法,也就是通常所说的抽象方法。u一个包含纯虚方法的类称之为抽象类,抽象类不能被实例化。通常用于实现接口的定义。
注意:抽象类不能实例化 ( CEmployee Worker;违规操作)
#define MAXLEN 128
class CEmployee // 包含抽象方法的类称之为抽象类
{
public:
int m_ID;
char m_Name[MAXLEN];
char m_Depart[MAXLEN];
virtual void outputName() = 0; // 定义抽象方法
};
class COpreate : public CEmployee
{
public:
char m_Password[MAXLEN];
void outputName() //
{
printf("操作员姓名: %s\n", m_Name);
}
COpreate()
{
strcpy_s(m_Name, "MR");
}
};
class CSystemManger : public CEmployee
{
public:
char m_Password[MAXLEN];
void outputName()
{
printf("系统管理员姓名: %s\n", m_Name);
}
CSystemManger()
{
strcpy_s(m_Name, "SK");
}
};
int main()
{
CEmployee *pWorker;
pWorker = new COpreate();
pWorker->outputName(); // 调用 COpreate 类对象的方法
delete pWorker;
pWorker = NULL;
pWorker = new CSystemManger();
pWorker->outputName(); // 调用 CSystemManger 类对象的方法
delete pWorker;
pWorker = NULL;
return 0;
}
注: 抽象类通常作为其他类的父类,从抽象类派生的子类如果也是抽象类,则子类必须实现父类中所有的纯虚方法。
实例化类对象的创建和释放过程:
当从父类派生一个子类后,定义一个子类的对象时,它将依次调用父类的构造函数、当前类的构造函数来创建对象。在释放子类对象时,先调用的是当前类的析构函数,然后是父类的析构函数。
指针类对象的创建和释放的过程:
定义一个基类类型的指针,调用子类的构造函数为其创建对象时。对象释放后,如果析构函数为虚函数,则先调用子类的析构函数,再调用基类的析构函数。如果析构函数不是虚函数时,则只会调用基类的析构函数(会发生内存泄露,因为子类中会为某一些数据成员在堆中分配了了空间,程序结束却没有释放)。
所以析构函数最好是保证是虚函数!!!
1.5 友元类和友元方法(函数)
类的私有方法,只有在该类中才允许访问,其他类是不能访问的,打死你hi如果两个类的耦合度比较紧密,在一个类中访问另一个类的私有成员就会带来极大的方便。C++提供了友元类和友元函数,来实现类与类之间的函数方法的访问。
当用户希望另一个类能够访问当前类的私有成员时,可以在当前类中将另一个类作为自己的友元类。
class CItem
{
private:
char m_Name[128];
void outputname()
{
printf("%s/n",m_Name);
}
public:
friend class CList; // 将CList类定义一个CItem的友元类
void SetItemName(const char * pchData)
{
if (pchData != NULL)
{
strcpy_s(m_Name, pchData);
}
}
CItem()
{
memset(m_Name,0, 128);
}
};
class CList
{
public:
void outputItem();
private:
CItem m_Item;
};
void CList::outputItem()
{
m_Item.SetItemName("BIEJING"); // 调用 CItem类的公有方法
m_Item.outputname(); // 调用 CItem类的私有方法
}
除了上述代码所展示的友元类之外,还可以在某一个类中定义其他类的友元方法。另外,友元方法(函数)也可以是一个全局函数。
1.6 运算符重载
运算符重载需要遵守如下规则和注意事项;
注意事项:
1、并不是所有的C++运算符都可以重载。比如“ ::”,“?”,“:”,“.”四个特殊运算符不能重载,其他均可。
限制:
1、不能创建新的运算符;
2、不能改变原有运算符操作数的个数;
3、不能改变又原有运算符的优先级;
4、不能改变原有运算符的结合性;
5、不能改变原有运算符的语法结构;
基本准则:
1、一元操作数可以是不带参数的成员函数。或者是带一个参数的非成员函数
2、二元函数可以是带一个参数的成员函数,或者是带两个参数的非成员函数、
3、 ” = “、” [] “、” ->“、“ ()”运算符只能定义为成员函数。
4、” ->“运算符和返回值必须是指针类型或者能够使用 ” ->“ 的运算符对象。
5、重载 ++ 和 -- 运算符时,带一个int类型参数,表示后置运算,不带参数表示前置运算。