-
C++教程之类、实例和对象
类、实例和对象
类是从 C 语言中的结构体演变而来,结构体的成员变量也就演化成类的成员变 量,这时类只能存放数据。为了在类内部操纵这些数据,增加了成员函数的功能。 所谓成员函数就是在类的内部定义,用来操作类的成员变量的函数。随后对成员变 量和成员函数附上“私有”、“保护”和“公共”的访问权限,于是“类”便大致成型。事实 上,C++中结构体的功能大致和类相当,也有了成员函数。“成员”是成员变量和成 员函数的统称。 类的出现带动设计风格的巨大变化, 与传统设计方法相区别, 称之为“面向对象 设计”。关于面向对象设计、继承、封装、派生等概念性知识不在本教程范围之内, 书店里参考书象山一样,这儿只请大家对三个概念不要混淆:类、实例和对象。 “类”和结构体一样,是一种自定义的数据类型,但不是基本类型。“实例”是用 自己定义的“类”这个数据类型来定义的变量。这些一个一个的实例统称“对象”。另 外,“继承”和“派生”是同一件事的不同说法,B 类继承了 A 类,也就是说 A 类派生 了 B 类。
class 类名 访问符:
{
成员变量定义; 成员函数定义;
访问符:
成员变量定义; 成员函数定义;
访问符:
成员变量定义; 成员函数定义; …… }
//| //| //| //| //| //| //| //| //| //| //| //|
class CSample public: int x1;
CSample();
{
protected: int a; float sum(float f1, float f2); private: int m; double sum(double f1, double f2);
…… }
一般按习惯将 private:定义部分放在紧靠类名下面,并且 “private:”可以省略。“private:”下面定义的成员全是“私有”的,也就是只能在这个类 的成员函数里可以使用, 外部(包括派生的子类)不能使用。 “public:”下面定义的成员, 所有地方都能使用。 “protected:”下面定义的成员, 在派生的子类中则相当于“public”, 其它地方则相当于“private”。
二、构造函数和析构函数
“构造函数”是类产生实例时被调用,进行初始化设置,是一个特殊的成员函数, 函数名与类名相同,没有返回值。一般构造函数定义在“public:”下面,但有时为了 阻止多个实例的产生而特意定义在“private:”或“protected:”下面。当初始化时没有什 么需要设定时也可以不定义,编译时会自动生成一个默认的构造函数。构造函数的 重载使得实例的生成灵活而且方便, 默认的构造函数没有参数, 且是定义在“public:” 下面的。 “析构函数”是类的实例被销毁时调用,进行最后的处理,例如释放动态分配的 内存等。一般析构函数定义在“public:”下面,不需要时也可以不定义,编译时会自 动生成一个默认的析构函数。析构函数的函数名与类名相同,前面有“~”返回值。 下面的例子演示构造函数和析构函数被调用的顺序。
#include using namespace std; class CA { int a; public:
CA(){ a = 0; cout << "构造函数: " << a << endl; } ~CA(){ cout << "析构函数: " << a << endl; }
void
}
setA(int x) {
a = x;
void
} };
print() {
cout << "print: " << a << endl;
int
{ CA ca;
main ( )
//ca.a = 10;
ca.setA(10); ca.print();
//成员变量 a 是私有的,不能直接访问
return
}
0;
三、成员函数的定义和声明分开
上面例子是将成员函数的定义和声明全写在类的定义体里面,更好的编程风格 是分开来写,也就是类定义体里面只写成员变量和成员函数的声明,而成员函数的 定义则写在类的定义体外。这样,类的定义体就可以移到“头文件”中去。在外部定 义时,成员函数名前面要加上“类名::”。
//==test.h== class CA { void a; public:
CA(); ~CA();
void void
};
setA(int x); print();
//==test.cpp== #include #include "test.h"; using namespace std;
CA::CA(){ CA:: a = 0; cout << "构造函数: " << a << endl; } CA::~CA(){ CA:: cout << "析构函数: " << a << endl; }
void
CA::setA(int x) { CA::
a = x;
}
void
}
CA::print() { CA::
cout << "print: " << a << endl;
int
{
main ( )
CA ca;
//ca.a = 10;
ca.setA(10); ca.print();
//成员变量 a 是私有的,不能直接访问
return
}
0;
四、生成实例的 3 种方法
了解生成实例的三种方法的细微区别是很重要的。①申明为变量,②从无名对 象复制,③申明为指针并动态生成。注意:指针的成员用“->”,而不用“.”。
//==test.cpp== #include #include " using namespace std; int
{ string strA("劝学网"); main ( )
//直接调用构造函数生成实例
cout << strA << strA.length() << endl;
//先调用构造函数生成空字符串实例 strB = string("小雅"); //再调用构造函数生成无名实例,然后复制 给 strB 实例,无名实例立即销毁 cout << strB << strB.length() << endl; //这和上面①的方法的结果相 同
string strB;
//先定义一个指针,尚未分配空间 strC = new string("quan.cn"); //动态调用构造函数生成实例后,再 将实例地址赋给指针变量
string *strC; cout << *strC << strC->length() << endl;
delete return
} 0;
strC;
//千万不要忘记释放
06 章 成员函数和运算符的重载
一、构造函数的重载
构造函数可以重载,使得生成实例时非常方便。构造函数一般要对成员变量赋 初值,有 2 种写法:
#include #include using namespace std; class
stuff {
string name;
int age; public:
stuff() {
//这是写法一
cout << name << "" << age << endl; "空 name = "空"; age = 0; cout << name << "" << age << endl; } stuff(string n, { cout << name << "" << age << endl; } string getName() {
int
a):name(n),age(a) :name(n),age(a)
//这是写法二
return
}
name;
int
} };
getAge() {
return
age;
int
{
main ( )
stuff st2; stuff st1("小雅", 27);
return
}
0;
写法一是在构造函数体中赋值, 赋值前成员变量已经有了地址空间, 尚未有值。 写法二是一种特殊方法,是在成员变量分配空间的同时将参数的值赋给成员变量。 虽然写法二用的人少,但明显优于写法一。 事实上, 如果将成员变量的定义改为常量, “const string name;”和“const int age;”, 写法一将出错,而写法二仍然正确。
二、运算符重载
运算符重载对于普通函数和成员函数来说,格式稍有不同。
//单目运算符
成员函数: 普通函数:
//双目运算符
返回值类型 返回值类型
operator operator
运算符 运算符
() ; (对象的类型) ;
成员函数: 普通函数:
//函数调用
返回值类型 返回值类型
operator operator
运算符
(对象的类型) ;
运算符 (对象的类型 1, 对象的类型 2) ;
成员函数:
//数组元素
返回值类型
operator
(任意的参数列) ;
成员函数:
//增 1/减 1 运算符
返回值类型
operator[]
(参数类型) ;
成员函数: 普通函数:
返回值类型 返回值类型
operator operator
运算符 运算符
(int) ; (对象的类型, int) ;
#include #include using namespace std; class
stuff {
string name;
int age; public:
stuff(string n, {
int
a):name(n),age(a)
cout << name << "" << age << endl; } string getName() {
return
}
name;
int
}
getAge() {
return void void
};
age;
operator +(int x); operator +(string s);
//运算符重载的定义 //运算符重载的定义
void
{
stuff::operator +(int x)
//运算符重载的实装
age = age + x; }
void
{
stuff::operator +(string s)
//运算符重载的实装
name = name + s; }
int
{
main ( )
stuff st2("小雅", 27); st2 + 3; st2 + ".诗经";
//+运算 //+运算
cout << st2.getName() << st2.getAge() << endl;
return
}
0;
三、拷贝构造函数和赋值运算符
本节内容较深,初学者请跳过。“拷贝构造函数”和“赋值运算符”都是将对象的 值复制一份然后传给另一对象。这二个功能也是类本身就具有的,但有很多场合原 封不动地复制给另外一个对象时反而会出错,例如在成员函数中有动态分配内存, 或者参数指针指向外部某一地址时,就有可能出错。
要避免这些错误,我们可以重载“=”运算符以及拷贝构造函数, 将出错的因素排除。下例中为了演示,故意将赋值运算符重载函数 中不复制“姓名”,而拷贝构造函数中固定“年龄”。
#include #include using namespace std; class
stuff {
string name;
int age; public:
stuff(string n, { cout << "构造函数 " << name << age << endl; } string getName() {
int
a):name(n),age(a)
return
}
name;
int
}
getAge() {
return
stuff&
age;
operator
stuff(stuff& {
//赋值运算符重载 x):name(x.name),age(20) //拷贝构造函数重载
=(stuff& x); " << name << age << endl;
cout << "拷贝构造函数 } };
stuff& stuff::operator =(stuff& x) { age = x.age; cout << "赋值运算符 " << name << age << endl;
return *this;
}
int
{
main ( )
stuffst("小雅", 25); stuff st1("劝学网", 2);
//调用通常的构造函数 //调用通常的构造函数
st1 = st; stuff st2 = st;
//因为不产生新的实例,所以调用的是赋值运算符 //因为产生新的实例,所以调用的是拷贝构造函数
cout << st.getName() << st.getAge() << endl; cout << st1.getName() << st1.getAge() << endl; cout << st2.getName() << st2.getAge() << endl;
return
}
0;
四、类型转换
当需要将当前类的实例直接赋值给其它类型的变量时自动转换类型,这其实还 是运算符重载。当需要其它类型直接赋值给当前类的实例时,只要增加构造函数就 行。
#include #include using namespace std; class
stuff {
string name;
int age; public:
stuff(string n,
int
a):name(n),age(a) { }
string getName() {
return
}
name;
int
}
getAge() {
return
age;
operator int() return age;
}
{
//stuff →
int
operator string() return name;
} };
{
//stuff →
string
int
{
main ( )
stuff st("小雅", 25); string m_name = st;
int
m_age = st;
//stuff → string //stuff → int
cout << m_name << endl; cout << m_age << endl;
return
}
0;
类是从 C 语言中的结构体演变而来,结构体的成员变量也就演化成类的成员变 量,这时类只能存放数据。为了在类内部操纵这些数据,增加了成员函数的功能。 所谓成员函数就是在类的内部定义,用来操作类的成员变量的函数。随后对成员变 量和成员函数附上“私有”、“保护”和“公共”的访问权限,于是“类”便大致成型。事实 上,C++中结构体的功能大致和类相当,也有了成员函数。“成员”是成员变量和成 员函数的统称。 类的出现带动设计风格的巨大变化, 与传统设计方法相区别, 称之为“面向对象 设计”。关于面向对象设计、继承、封装、派生等概念性知识不在本教程范围之内, 书店里参考书象山一样,这儿只请大家对三个概念不要混淆:类、实例和对象。 “类”和结构体一样,是一种自定义的数据类型,但不是基本类型。“实例”是用 自己定义的“类”这个数据类型来定义的变量。这些一个一个的实例统称“对象”。另 外,“继承”和“派生”是同一件事的不同说法,B 类继承了 A 类,也就是说 A 类派生 了 B 类。
class 类名 访问符:
{
成员变量定义; 成员函数定义;
访问符:
成员变量定义; 成员函数定义;
访问符:
成员变量定义; 成员函数定义; …… }
//| //| //| //| //| //| //| //| //| //| //| //|
class CSample public: int x1;
CSample();
{
protected: int a; float sum(float f1, float f2); private: int m; double sum(double f1, double f2);
…… }
一般按习惯将 private:定义部分放在紧靠类名下面,并且 “private:”可以省略。“private:”下面定义的成员全是“私有”的,也就是只能在这个类 的成员函数里可以使用, 外部(包括派生的子类)不能使用。 “public:”下面定义的成员, 所有地方都能使用。 “protected:”下面定义的成员, 在派生的子类中则相当于“public”, 其它地方则相当于“private”。
二、构造函数和析构函数
“构造函数”是类产生实例时被调用,进行初始化设置,是一个特殊的成员函数, 函数名与类名相同,没有返回值。一般构造函数定义在“public:”下面,但有时为了 阻止多个实例的产生而特意定义在“private:”或“protected:”下面。当初始化时没有什 么需要设定时也可以不定义,编译时会自动生成一个默认的构造函数。构造函数的 重载使得实例的生成灵活而且方便, 默认的构造函数没有参数, 且是定义在“public:” 下面的。 “析构函数”是类的实例被销毁时调用,进行最后的处理,例如释放动态分配的 内存等。一般析构函数定义在“public:”下面,不需要时也可以不定义,编译时会自 动生成一个默认的析构函数。析构函数的函数名与类名相同,前面有“~”返回值。 下面的例子演示构造函数和析构函数被调用的顺序。
#include using namespace std; class CA { int a; public:
CA(){ a = 0; cout << "构造函数: " << a << endl; } ~CA(){ cout << "析构函数: " << a << endl; }
void
}
setA(int x) {
a = x;
void
} };
print() {
cout << "print: " << a << endl;
int
{ CA ca;
main ( )
//ca.a = 10;
ca.setA(10); ca.print();
//成员变量 a 是私有的,不能直接访问
return
}
0;
三、成员函数的定义和声明分开
上面例子是将成员函数的定义和声明全写在类的定义体里面,更好的编程风格 是分开来写,也就是类定义体里面只写成员变量和成员函数的声明,而成员函数的 定义则写在类的定义体外。这样,类的定义体就可以移到“头文件”中去。在外部定 义时,成员函数名前面要加上“类名::”。
//==test.h== class CA { void a; public:
CA(); ~CA();
void void
};
setA(int x); print();
//==test.cpp== #include #include "test.h"; using namespace std;
CA::CA(){ CA:: a = 0; cout << "构造函数: " << a << endl; } CA::~CA(){ CA:: cout << "析构函数: " << a << endl; }
void
CA::setA(int x) { CA::
a = x;
}
void
}
CA::print() { CA::
cout << "print: " << a << endl;
int
{
main ( )
CA ca;
//ca.a = 10;
ca.setA(10); ca.print();
//成员变量 a 是私有的,不能直接访问
return
}
0;
四、生成实例的 3 种方法
了解生成实例的三种方法的细微区别是很重要的。①申明为变量,②从无名对 象复制,③申明为指针并动态生成。注意:指针的成员用“->”,而不用“.”。
//==test.cpp== #include #include " using namespace std; int
{ string strA("劝学网"); main ( )
//直接调用构造函数生成实例
cout << strA << strA.length() << endl;
//先调用构造函数生成空字符串实例 strB = string("小雅"); //再调用构造函数生成无名实例,然后复制 给 strB 实例,无名实例立即销毁 cout << strB << strB.length() << endl; //这和上面①的方法的结果相 同
string strB;
//先定义一个指针,尚未分配空间 strC = new string("quan.cn"); //动态调用构造函数生成实例后,再 将实例地址赋给指针变量
string *strC; cout << *strC << strC->length() << endl;
delete return
} 0;
strC;
//千万不要忘记释放
06 章 成员函数和运算符的重载
一、构造函数的重载
构造函数可以重载,使得生成实例时非常方便。构造函数一般要对成员变量赋 初值,有 2 种写法:
#include #include using namespace std; class
stuff {
string name;
int age; public:
stuff() {
//这是写法一
cout << name << "" << age << endl; "空 name = "空"; age = 0; cout << name << "" << age << endl; } stuff(string n, { cout << name << "" << age << endl; } string getName() {
int
a):name(n),age(a) :name(n),age(a)
//这是写法二
return
}
name;
int
} };
getAge() {
return
age;
int
{
main ( )
stuff st2; stuff st1("小雅", 27);
return
}
0;
写法一是在构造函数体中赋值, 赋值前成员变量已经有了地址空间, 尚未有值。 写法二是一种特殊方法,是在成员变量分配空间的同时将参数的值赋给成员变量。 虽然写法二用的人少,但明显优于写法一。 事实上, 如果将成员变量的定义改为常量, “const string name;”和“const int age;”, 写法一将出错,而写法二仍然正确。
二、运算符重载
运算符重载对于普通函数和成员函数来说,格式稍有不同。
//单目运算符
成员函数: 普通函数:
//双目运算符
返回值类型 返回值类型
operator operator
运算符 运算符
() ; (对象的类型) ;
成员函数: 普通函数:
//函数调用
返回值类型 返回值类型
operator operator
运算符
(对象的类型) ;
运算符 (对象的类型 1, 对象的类型 2) ;
成员函数:
//数组元素
返回值类型
operator
(任意的参数列) ;
成员函数:
//增 1/减 1 运算符
返回值类型
operator[]
(参数类型) ;
成员函数: 普通函数:
返回值类型 返回值类型
operator operator
运算符 运算符
(int) ; (对象的类型, int) ;
#include #include using namespace std; class
stuff {
string name;
int age; public:
stuff(string n, {
int
a):name(n),age(a)
cout << name << "" << age << endl; } string getName() {
return
}
name;
int
}
getAge() {
return void void
};
age;
operator +(int x); operator +(string s);
//运算符重载的定义 //运算符重载的定义
void
{
stuff::operator +(int x)
//运算符重载的实装
age = age + x; }
void
{
stuff::operator +(string s)
//运算符重载的实装
name = name + s; }
int
{
main ( )
stuff st2("小雅", 27); st2 + 3; st2 + ".诗经";
//+运算 //+运算
cout << st2.getName() << st2.getAge() << endl;
return
}
0;
三、拷贝构造函数和赋值运算符
本节内容较深,初学者请跳过。“拷贝构造函数”和“赋值运算符”都是将对象的 值复制一份然后传给另一对象。这二个功能也是类本身就具有的,但有很多场合原 封不动地复制给另外一个对象时反而会出错,例如在成员函数中有动态分配内存, 或者参数指针指向外部某一地址时,就有可能出错。
要避免这些错误,我们可以重载“=”运算符以及拷贝构造函数, 将出错的因素排除。下例中为了演示,故意将赋值运算符重载函数 中不复制“姓名”,而拷贝构造函数中固定“年龄”。
#include #include using namespace std; class
stuff {
string name;
int age; public:
stuff(string n, { cout << "构造函数 " << name << age << endl; } string getName() {
int
a):name(n),age(a)
return
}
name;
int
}
getAge() {
return
stuff&
age;
operator
stuff(stuff& {
//赋值运算符重载 x):name(x.name),age(20) //拷贝构造函数重载
=(stuff& x); " << name << age << endl;
cout << "拷贝构造函数 } };
stuff& stuff::operator =(stuff& x) { age = x.age; cout << "赋值运算符 " << name << age << endl;
return *this;
}
int
{
main ( )
stuffst("小雅", 25); stuff st1("劝学网", 2);
//调用通常的构造函数 //调用通常的构造函数
st1 = st; stuff st2 = st;
//因为不产生新的实例,所以调用的是赋值运算符 //因为产生新的实例,所以调用的是拷贝构造函数
cout << st.getName() << st.getAge() << endl; cout << st1.getName() << st1.getAge() << endl; cout << st2.getName() << st2.getAge() << endl;
return
}
0;
四、类型转换
当需要将当前类的实例直接赋值给其它类型的变量时自动转换类型,这其实还 是运算符重载。当需要其它类型直接赋值给当前类的实例时,只要增加构造函数就 行。
#include #include using namespace std; class
stuff {
string name;
int age; public:
stuff(string n,
int
a):name(n),age(a) { }
string getName() {
return
}
name;
int
}
getAge() {
return
age;
operator int() return age;
}
{
//stuff →
int
operator string() return name;
} };
{
//stuff →
string
int
{
main ( )
stuff st("小雅", 25); string m_name = st;
int
m_age = st;
//stuff → string //stuff → int
cout << m_name << endl; cout << m_age << endl;
return
}
0;
最新更新
Objective-C语法之代码块(block)的使用
VB.NET eBook
Add-in and Automation Development In VB.NET 2003 (F
Add-in and Automation Development In VB.NET 2003 (8
Add-in and Automation Development in VB.NET 2003 (6
Add-in and Automation Development In VB.NET 2003 (5
AddIn Automation Development In VB.NET 2003 (4)
AddIn And Automation Development In VB.NET 2003 (2)
Addin and Automation Development In VB.NET 2003 (3)
AddIn And Automation Development In VB.NET 2003 (1)
2个场景实例讲解GaussDB(DWS)基表统计信息估
常用的 SQL Server 关键字及其含义
动手分析SQL Server中的事务中使用的锁
openGauss内核分析:SQL by pass & 经典执行
一招教你如何高效批量导入与更新数据
天天写SQL,这些神奇的特性你知道吗?
openGauss内核分析:执行计划生成
[IM002]Navicat ODBC驱动器管理器 未发现数据
初入Sql Server 之 存储过程的简单使用
SQL Server -- 解决存储过程传入参数作为s
武装你的WEBAPI-OData入门
武装你的WEBAPI-OData便捷查询
武装你的WEBAPI-OData分页查询
武装你的WEBAPI-OData资源更新Delta
5. 武装你的WEBAPI-OData使用Endpoint 05-09
武装你的WEBAPI-OData之API版本管理
武装你的WEBAPI-OData常见问题
武装你的WEBAPI-OData聚合查询
OData WebAPI实践-OData与EDM
OData WebAPI实践-Non-EDM模式