-
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#基于接口设计三层架构Unity篇
C#线程 入门
C#读取静态类常量属性和值
C# 插件式编程
C# 委托与事件有啥区别?
C#队列学习笔记:队列(Queue)和堆栈(Stack
linq 多表分组左连接查询查询统计
C#队列学习笔记:MSMQ入门一
C# 基础知识系列- 1 数据类型
二、C#入门—基础语法
C# 在Word中添加Latex 数学公式和符号
inncheck命令 – 检查语法
基于UDP的服务器端和客户端
再谈UDP和TCP
在socket编程中使用域名
网络数据传输时的大小端问题
socket编程实现文件传输功能
如何优雅地断开TCP连接?
图解TCP四次握手断开连接
详细分析TCP数据的传输过程
SqlServer 利用游标批量更新数据
BOS只读状态修改
SQL Server等待事件—PAGEIOLATCH_EX
数据库多行转换为单一列
获取数据表最后最后访问,修改,更新,
计算经历的时间
SQL查询结果自定义排序
修改数据库默认位置
日期简单加或减
从日期获取年,月或日