二、类
01 成员函数、对象拷贝、私有成员
1 综述
2 类基础
3 成员函数
4 对象的拷贝
5 私有成员
成员函数
//Time.h
class Time {
int hour;
int minute;
int second;
void InitTime (int tmphour, int tmpminute, int tmpsecond);
};
//Time.cpp
#include "Time.h"
void Time::InitTime (int tmphour, int tmpminute, int tmpsecond)
{
hour = tmphour;
minute = tmpminute;
second = tmpsecond; //类内部定义成员函数可以之间引用成员变量
}
对象的拷贝
Time time_one;
Time time_two = time_one;
Time time_three (time_two);
Time time_four { time_three };
默认情况下,这种类对象的拷贝,是每个成员变量逐个拷贝。如果在类中定义适当的“赋值运算符”就能够控制对象的这种拷贝行为。
02 构造函数详解,explicit,初始化列表
1 构造函数
2 多个构造函数
3 函数默认参数
4 隐式转换和explicit
5 构造函数初始化列表
构造函数
在类中,有一种特殊的成员函数,它的名字和类名相同,我们在创建类的对象时,这个特殊的成员函数就会被系统自动调用。这个成员函数,就叫“构造函数”;因为构造函数会被系统自动调用,所以我们可以简单的理解成:构造函数的目的就是初始化类对象的数据成员。
//Time.h
class Time {
public:
int Hour;
int Minute;
int Second;
Time (int tmphour, int tmpminute, int tmpsecond);
};
//Time.cpp
Time::Time (int tmphour, int tmpminute, int tmpsecond)
{
Hour = tmphour;
Minute = tmpminute;
Second = tmpsecond;
}
- 构造函数没有返回值
- 不可以手工调用构造函数
- 正常情况下,构造函数应该被声明为public
- 构造函数中如果有多个参数,则我们创建对象的时候也要带上这些参数
//定义一个类对象
Time mytime = Time(3, 29, 23);
Time mytime(3, 29, 23);
Time mytime{3, 29, 23);
Time mytime = Time{3, 29, 23};
多个构造函数
提供多种初始化方法,但是参数表类型或者个数要有明显不同。
通过对象拷贝得到的新对象并没有调用传统意义上的构造函数而是调用了拷贝构造函数。
函数默认参数
- 函数的默认值只能放在函数声明中,除非该函数没有函数声明
- 在具有多个参数的函数中指定默认值时,默认参数都必须出现在不默认参数的右边
- 一旦某个参数指定了默认值,它右边的所有参数都必须制定默认值
隐式转换和explicit
在构造函数声明前添加explicit
可以强制性要求构造函数不能做隐式类型转换,只能用于初始化和显式类型转换。
构造函数初始化列表
定义构造函数时
Time::Time (int tmphour, int tmpminute, int tmpsecond)
:Hour(tmphour), Minute(tmpminute), Second(tmpsecond)
{
...;
}
03 inline、const、mutable、this、static
1 在类定义中实现成员函数inline
2 成员函数末尾的const
3 mutable
4 返回自身对象的引用,this
5 static成员
在类定义中实现成员函数inline
直接在.h头文件中类的定义中实现的成员函数,会被当作inline内联函数来处理。
成员函数末尾的const“常量成员函数”
在成员函数后增加一个const。不但要在成员函数声明中增加const,在函数定义时也要增加const。
作用:这个成员函数不会修改该对象里任何成员变量的值。
void classname::funcname (int valuelist) const;
结论:const成员函数,不管是const对象和非const对象都可以调用常量成员函数;而普通成员函数,不能用const对象调用。
mutable
不稳定,易改变的意思。为了图片const的限制
用mutable
修饰一个成员变量,一个成员变量一旦被mutable修饰了,就表示这个成员变量永远处于可以被修改状态。即便是在const结尾的成员函数中,也可以修改。
返回自身对象引用this
快捷键
ctrl + F3 + fn
查找文件中重名项
//Time.h
class Time {
int Hour;
Time& add_hour (int tmphour);
};
//Time.cpp
Time& Time::add_hour (int tmphour) //返回值是对象本身
{
Hour += tmphour; //this->Hour += tmphour;
return *this; //this就是指向对象本身的指针,*this就是对象本身
}
//project.cpp
#include "Time.h"
int main()
{
Time mytime;
mytime.add_hour(3);
}
在调用成员函数时,编译器负责把这个对象的地址(&mytime)传递给成员函数中一个隐藏的this形参。在系统角度看来,任何对类成员的直接访问都被看做是通过this做隐式调用的。
- this指针只能在成员函数中使用,全局函数、静态函数都不能使用this指针
- 在普通成员函数中,this是一个指向非const类型的const指针
(Time* const this)
指向不能变,值可以改变 - 在const成员函数中,this是一个指向const类型的const指针
(const Time* const this)
指向和值都不可以改变
static成员
属于整个类的成员变量,这种成员变量叫static成员变量(静态成员变量),同样也有静态成员函数。静态成员函数只能操作静态成员变量。
特点:不属于某个对象,属于整个类,一旦在某个对象中修改了这个成员变量的值,在其他对象中也会随之改变。
调用时类名::成员变量名``类名::成员函数名
//Time.h
class Time {
static int date; //还没有分配内存,不能初始化
};
//Time.cpp
int Time::date; //分配内存,不给初值默认为0,定义时不需要加static
04 类内初始化、默认构造函数、 =default
1 类相关非成员函数
2 类内初始化
3 const成员变量的初始化
4 默认构造函数
5 =default; ,=delete;
const成员变量的初始化
常量属性变量,需要在构造函数定义时使用初始化成员列表给出常量值。
默认构造函数
没有参数的构造函数称为默认构造函数。当没有构造函数时,进行“默认初始化”,编译器会生成一个“合成默认构造函数”。
使用多个构造函数时,一旦定义了一个构造函数系统就不会生成“默认构造函数”。
=default;
,=delete;
C++11引入
class Time {
public:
Time() = default; //编译器能够自动生成函数体(只能用于特殊的函数)
Time() = delete; //禁用某个函数
};
05 拷贝构造函数
如果一个类的构造函数的第一个参数是所属的类类型的引用。如果还有其他额外参数,那么这些额外的参数还都有默认值,则这个构造函数叫做拷贝构造函数。
class Time {
public:
Time (const Time& tmptime, int tmpvalue = 0);
};
Time::Time(const Time& tmptime, int tmpvalue){
}
拷贝构造函数不要使用explicit
。
06 重载运算符、拷贝赋值运算符、析构函数
1 重载运算符
2 拷贝赋值运算符
3 析构函数函数重载
构造函数的成员初始化
析构函数的成员销毁
new对象和delete对象
重载运算符
总结:
重载运算符本质上是一个函数。整个函数的正式名字:operator运算符
。重载运算符有返回类型和参数列表。
拷贝赋值运算符
class Time {
public:
Time& operator=(const Time& tmpin);
};
Time& Time::operator=(const Time& tmpin) {
return *this; //返回调用拷贝赋值运算符的对象本身
}
//调用举例
Time mytime1;
Time mytime2;
mytime2 = mytime1; //实际上是mytime2这个对象调用了拷贝赋值运算符,返回的仍是mytime2,形参是mytime1
析构函数:相对于构造函数
对象在销毁时,会自动调用析构函数
由~类名
构成,返回值,不接受任何参数,不能被重载,一个类只能用唯一一个析构函数。
~Time();
Time::~Time()
{
...
}
成员变量的初始化和销毁时机问题:初始化时先定义的变量先初始化,销毁时先定义的后销毁。
07 派生类、调用顺序、访问等级、函数遮蔽
1 派生类概念
2 派生类对象定义时调用构造函数的顺序
3 public,protected,private
4 函数遮蔽
派生类概念
类之间存在着一种层次关系**“继承”**,继承的概念是面向对象程序设计的核心思想之一
- 父类(基类,超类)
- 子类(派生类)
class Men : public Human //Men是Human的子类
继承方式(访问等级/访问权限):public
/protected
/private
一个子类可以继承多个父类
派生类对象定义时调用构造函数的顺序
首先会调用父类的构造函数,再调用子类的构造函数。
public
/private
/protected
三种访问权限:
public
:可以被任意实体所访问
protected
:只允许本类或者子类的成员函数来访问
private
:只允许本类的成员函数访问
三种继承访问:public
、protected
、private
基类中的访问权限 | 子类继承基类的继承方式 | 子类得到的访问权限 |
---|---|---|
public | public | public |
protected | public | protected |
private | public | 子类无权访问 |
public | protected | protected |
protected | protected | protected |
private | protected | 子类无权访问 |
public | private | private |
protected | private | private |
private | private | 子类无权访问 |
总结:
- 子类public继承父类不改变父类的访问权限
- protected继承将父类中public成员变成子类的protected成员
- private继承是的父类所有成员在子类中的访问权限变为private
- 父类中的private成员不受继承方式的额影响,子类永远无权访问
- 对于父类来讲,尤其是父类的成员函数,如果不想让外面访问,就设置为private;如果想让自己的子类能够访问,就设置为protected;如果想公开,就设置为public。
函数遮蔽
如果父类和子类中都出现同名函数,那么父类中无论有几个同名函数子类中都无法访问到。有两种方法:
- 在子类的成员函数中,用“父类::函数名”强制调用父类函数
- 通过
using
(C++11新定义)让父类同名函数在子类中可见,实际上就是通过“重载”的方式
08 基类指针、虚纯虚函数、多态性、虚析构
1 基类指针、派生类指针
2 虚函数override
final3 多态性
4 纯虚函数
5 基类的析构函数一般写成虚函数(虚析构函数)
基类指针、派生类指针
Human *phuman = new Men;
新玩法:用父类指针new
一个子类对象,但是这个父类指针不能调用子类成员函数
虚函数
Q:有没有一个解决方案,使我们只定义一个对象指针,就能够调用父类、子类的同名函数?
A:。但是它的类型必须是父类类型,而且该同名函数在父类声明之前要加virtual
声明该函数为虚函数。
一旦一个函数在基类中被声明成了虚函数,那么在所有的派生类中这个函数都是虚函数。
Human *phuman = new Human; //new基类,调用基类内的同名函数
phuman = new Men; //子类,调用子类内的同名函数
phuman = new Wemen;
override
这个关键字用在“子类”中,虚函数专用,用来说明派生类中的虚函数覆盖了父类中的同名函数,加在函数声明末尾。
class Men : public Human {
public:
virtual void eat() override;
};
final
虚函数专用,用在父类中函数声明中后加final
,那么就禁止一切覆盖该函数的操作。
调用虚函数执行的是“动态绑定”。动态:表示的是在程序运行时才能得知调用了哪个子类的虚函数。
多态性
多态性是针对虚函数来说的,体现在具有继承关系的父类和子类之间,子类重新定义父类的成员函数,通过父类的指针,只有到了程序运行时期,找到动态绑定到父类上的对象,系统内部通过查虚函数表,找到入口地址,从而调用父类或者子类的成员函数,这就是运行时期的多态性。
纯虚函数
纯虚函数是在基类中声明的虚函数,但是它在基类中没有定义,但是要求任何派生类都要定义该虚函数自己的实现方法。
基类中实现纯虚函数的方法是 在函数原型后加= 0
。
一旦一个类有纯虚函数,那么就不能生成这个类的对象了,它的主要目的是统一管理子类对象。
基类的析构函数一般写成虚函数(虚析构)
Q:用基类指针new子类的对象,在delete系统不会调用派生类的析构函数,导致内存泄漏。
A:在父类的析构函数前virtual
写成虚析构
结论:如果类作为基类,就一定要写析构函数~类名
,析构函数一定要是虚析构函数virtual
。
09 友元函数、友元类、友元成员函数
1 友元函数
2 友元类
3 友元成员函数
友元函数
在类中定义为private
不能公开访问,如果让函数function成为类的友元函数,那么就能访问类的所有成员(成员变量/成员函数)(private
/public
/protected
)。
class Name {
friend void function(const Name& tmpin); //该函数是友元函数
};
友元函数不属于类成员,不受public、private、protected管控。
友元类
class B {
public:
void callA(int x, A& tmpin) {
tmpin.a = x;
}
};
class A {
private:
int a;
friend class B; //B是A的友元类,B可以调用A中的所有成员
};
每个类负责控制自己的友元类和友元函数
- 友元关系不能被继承
- 友元关系是单向的
- 友元关系是没有传递性的
友元成员函数
只有public
的函数才能成为其他类的友元成员函数
10 RTTI、dynamic_cast、typeid、虚函数表
1 RTTI是什么
2dynamic_cast
运算符
3 typeid运算符
4 type_info类
5 RTTI与虚函数表
RTTI是什么
RTTI(Run Time Identification):运行时类型识别
通过运行时类型识别,程序能够使用基类的指针或者引用来检查这些指针或者引用所指的对象的实际派生类型。
RTTI我们把这个称呼看成是一种系统提供的一种能力,或者一种功能。这种功能或者能力通过两个运算符来体现:
dynamic_cast
运算符:能够将基类的指针或者引用安全的转换为派生类的指针或者引用typeid
运算符:返回指针或者引用所指对象的实际类型
补充:要让RTTI的两个运算符能够正常工作,基类中必须要有一个虚函数
作用:前面讲到,基类和派生类的同名函数可以通过虚函数和父类指针new子类对象来实现,但是子类中专有函数却不能通过该指针调用,RTTI就用来解决这个问题
dynamic_cast
运算符
Human* phuman = new Man;
Man* pman = dynamic_cast<Man*>(phuman);
基类必须有一个虚函数。
对于引用,如果用dynamic_cast
转换失败,会返回一个std::bad_cast
异常。C++中我们使用try{}...catch(){}:
捕捉异常。
Human* phuman = new Men;
Human& q = *phuman;
try
{
Men& men_in = dynamic_cast<Men&>(q);
men_in.test();
}
catch(std::bad_cast)
{
cout << "phuman实际不是一个Men类型" << endl;
}
typeid
运算符
typeid(类型),也可能typeid(表达式)
拿到对象类型信息;typeid就会返回一个常量对象的引用,这个常量对象是一个标准库类型type_info(类)。
Human* phuman = new Men;
Human& q = *phuman;
cout << typeid(*phuman).name() << endl; //typeid返回值是一个类,.name()是一个成员
cout << typeid(q).name() << endl;
type_info类
typeid().name()
返回名字typeid
返回的是type_info这个类,可以用const type_info &tp
接
RTTI与虚函数表
C++中,如果类里含有虚函数。编译器就会对该类产生一个虚函数表。虚函数表里每一项都是一个指针。每个指针指向这个类里的各个虚函数的入口地址。虚函数表项里,第一个表项很特殊,它指向的是这个类所的type_info对象。
11 基类与派生类关系的详细再探讨
1 派生类对象模型简述
2 派生类构造函数
3 既当父类又当子类
4 不想当基类的类
5 静态类型与动态类型
6 派生类向基类的隐式类型转换
7 父类子类之间的拷贝与赋值
派生类对象模型简述
派生类对包含含有派生类自己定义的成员变量、成员函数的子对象和所继承的基类的子对象。
派生类构造函数
派生类用基类的构造函数初始化基类部分,派生类的构造函数初始化派生类部分。
Q:假如基类构造函数需要传入参数,派生类该如何定义?
A:通过派生类的构造函数初始化列表。
class A {
public:
A(int i) : m_value_a(i) {}
int m_value_a;
};
class B : public A {
public:
B(int i, int j) : m_value_b(j), A(i) {}
int m_value_b;
};
既当父类又当子类
继承关系有传递性,构成一种继承链。
不想当基类的类
final
加在类名后,就不能做基类了。
final
在前面讲到是不能被子类中的同名函数遮蔽。
静态类型与动态类型
静态类型:变量声明时的类型。静态类型编译的时候是已知的。
动态类型:指的是这个指针或引用所代表的内存中的对象的类型,在运行时才能得知。
只有在基类指针或引用时才存在静态类型和动态类型不一致的情况。
用派生类对象给一个基类对象赋值或初始化时,只会对派生类对象的基类部分操作。
复习:
class Human {
public:
//重载拷贝运算符
Human& operator=(const Human& tmphuman)
{
return *this;
}
//拷贝构造函数
Human (Human& tmpin) {}
};
12 左值、右值、左值引用、右值引用、move
1 左值和右值
2 引用分类
3 左值引用
4 右值引用右值引用的引入目的
5 std::move函数
6 左值右值总结说明
左值和右值
左值(左值表达式):能用在赋值语句等号左侧的东西,能够代表一个地址。
右值(右值表达式):非左值的值。
一个左值可能同时具有左值属性和右值属性。
引用分类
三种形式的引用:
- 左值引用(绑定到左值)
&
- const引用(常量引用)(也是左值引用,但是不能改变值的对象)
- 右值引用(绑定到右值)
&&
左值引用
除const引用可以绑定到右值外,其他左值引用均不可以绑定到右值。
右值引用
int&& refrightvalue = 3;
总结:
- 返回左值引用的函数,连同赋值,下标,前置递增递减运算符(++i),都是返回左值表达式的例子
- 返回非引用类型的函数,连同算数,关系,位以及后置递增运算符(i++)都生成右值
++i
是左值表达式的原因:++i直接给变量i+1,然后返回i本身,这里i是变量。
i++
**是右值表达式的原因:**i++先产生一个临时变量,记录下临时变量的值用作使用,在递增接着返回这个临时变量,临时变量是右值。
右值引用的引入目的
- 提高系统运行效率。把拷贝对象变成移动对象来提高效率
- 移动对象。
&&
作为移动构造函数和移动赋值运算符的参数,&
作为拷贝赋值运算符和拷贝赋值构造函数的参数
std::move函数
把左值强制转换成右值
int i = 10;
int&& ri = std::move(i)
13 临时对象深入探讨、解析,提高性能手段
1 临时对象的概念
2 产生临时对象的情况和解决以传值的方式给函数传递参数
类型转换生成的临时对象/隐式类型转换以保证函数调用成功
函数返回对象的时候
临时对象的概念
一些临时变量是因为代码书写问题产生的统一称临时变量为临时对象。
产生临时对象的情况和解决
以传值的方式给函数传递参数
应当避免直接将对象实体传入函数,如果传入的是类对象,会多调用拷贝构造函数和析构函数,消耗系统资源。
类型转换生成的临时对象/隐式类型转换以保证函数调用成功
C++语言只会给const引用产生临时变量。
class Cls {
public:
int value1;
int value2;
Cls (int tmpin1 = 0, int tmpin2 = 0) : value1(tmpin1), value2(tmpin2);
};
Cls operator+ (Cls& cls_in1, Cls& cls_in2)
{
return Cls(cls_in1.value1 + cls_in2.value1, cls_in1.value2 + cls_in2.value2);
}
直接return避免构建局部对象,节省了构造函数和析构函数的使用提升效率。
14 对象移动、移动构造函数、移动赋值运算符
1 对移动的概念
2 移动构造函数和移动赋值运算符概念
3 移动构造函数演示
4 移动赋值运算符演示
5 合成的移动操作
6 总结
对移动的概念
C++11定义的新概念,将临时对象有用的部分保留下来,无用的部分清除掉
移动构造函数和移动赋值运算符概念
首先回顾一下,拷贝赋值运算符在定义时返回值类型和参数表都是根据具体情况而变的,唯一不变的是函数名必须是operator符号
移动构造函数:类名::类名(const 类名&& tmp)
右值引用的引入用作移动构造函数的参数,移动构造函数除右值引用外其他函数参数都必须带有初值(拷贝构造函数也是这样)
移动构造函数和移动赋值运算符应该完成的功能
- 完成必要的内存移动,斩断原对象和内存的关系
- 确保移动后原对象处于一种可以销毁对程序没有影响的状态
移动构造函数演示
class B {
public:
int b_value;
B() : b_value(100){
cout << "调用了class B的构造函数" << endl;
}
B(const B& tmpb) : b_value(tmpb.b_value){
cout << "调用了class B的拷贝构造函数" << endl;
}
virtual ~B(){
cout << "调用class B的析构函数" << endl;
}
};
class A {
private:
B* pb;
public:
A() : pb(new B()){ //用new B()对象递给pb这个指针,调用了B的构造函数
cout << "调用了class A的构造函数" << endl;
}
A(const A& tmpa) : pb(new B(*(tmpa.pb))){
cout << "调用了class A的拷贝构造函数" << endl; //其实还调用了B的拷贝构造函数,用已存在的tmpa(A类对象)的pb指针,传递给B的拷贝构造函数得到新的类B赋给新指针pb
}
//移动构造函数
//直接把传入对象的指针(地址)给了临时对象
A(A&& tmpa) noexcept : pb(tmpa.pb){ //参数不能是const类,因为需要改动
tmpa.pb = nullptr; //打断传入对象的指针和指向对象的联系
cout << "调用了class A的移动构造函数" << endl;
}
virtual ~A(){
delete pb;
cout << "调用了class A的析构函数" << endl;
}
};
noexcept
是C++11标准,目的是通知标准库这个移动构造函数不抛出任何异常(提高编译器工作效率)。
移动赋值运算符
//拷贝赋值运算符
A& operator=(const A& src){
if (pb == &src)
return *this;
delete pb;
pb = new B(*(src.pb));
return *this;
}
//移动赋值运算符
A& operator=(A&& src) noexcept{
if (this == &src)
return *this;
delete pb; //先把自己的pb内存干掉
pb = src.pb; //把传入对象的成员内存拿走
src.pb = nullptr; //斩断源(把对方和内存的关联斩断)
return *this;
}
合成的移动操作
- 如果类有拷贝构造函数/拷贝赋值运算符/析构函数,那么编译器就不会合成移动构造函数和移动赋值运算符
- 如果没有移动构造函数/移动赋值运算符,系统会调用拷贝构造函数/拷贝赋值运算符
- 只有一个类没定义任何拷贝构造成员,且类的每个非静态成员都可以移动时,系统会自动合成移动操作
15 继承的构造函数、多重继承、虚继承
1 继承的构造函数
2 多重继承多重继承概述
静态成员变量
派生类构造函数与析构函数
从多个父类继承构造函数3 类型转换
4 虚基类、虚继承(虚派生)
5 总结
继承的构造函数
一个类只继承其直接基类(父类)的构造函数。
using A::A;
继承A的构造函数。using
会让某个名字在当前作用域内可见。
会把基类中每个构造函数,都生成一个与之对应的派生类构造函数(除拷贝构造函数/移动构造函数)。
当基类A的构造函数有默认参数时,编译器会构造出多个派生的构造函数。第一个构造函数是带有所有参数的构造函数,其余的构造函数,每个分别省略掉一个默认参数。
多重继承
一个类同时继承多个类
class C : public A, public B
子类(派生类)必须在构造函数中给父类构造函数初始化
静态成员变量
class A {
public:
static int a; //声明一个静态成员
};
int A::a = 1; //定义静态成员变量(分配内存)
派生类构造函数与析构函数
- 构造一个派生类对象将构造并初始化所有基类子对象
- 派生类的构造函数初始化列表只初始化它的直接基类
- 派生类的构造函数初始化列表将实参分别传递给每个直接基类;基类的构造顺序和派生列表中基类的出现顺序保持一致
虚基类、虚继承(虚派生)
派生列表中,同一个基类只能出现一次,除两种情况外:
- 派生类可以通过两个直接基类分别继承同一个间接基类
- 直接继承某个基类,然后通过另一个基类间接继承该类
虚基类:无论继承体系中出现多少次基类,都只会出现唯一一个共享的虚基类子内容。
class A : virtual public B {
};
如果通过间接继承同一个基类,使用虚基类,需要用这个间接的子类初始化重复继承的那个基类,换句话说虚基类需要由最底层派生类初始化。
插入课:C++文件和流
标准库fstream(file stream文件流)
数据类型 | 描述 |
---|---|
ofstream | 该数据类型标书输出文件流,用于创建文件并向文件写入信息 |
ifstream | 该数据类型表示输入文件流,用于从文件读取信息 |
fstream | 该数据类型表示文件流,同时具有ofstream和ifstream两种功能 |
在C++中进行文件处理必须包含头文件和
打开文件
open()函数是fstream、ifstream、ofstream对象的一个成员,它的标准语法是
void open(const char* filename, ios::openmode mode);
//void std::fstream::open(const char *_Filename, unsigned int _Mode)
第一个参数是文件的名称和位置,第二个参数定义文件被打开的模式。
模式标志 | 描述 |
---|---|
ios::app | 追加模式。所有写入都追加到文件末尾 |
ios::ate | 文件打开后定位到文件末尾 |
ios::in | 打开文件用于读取 |
ios::out | 打开文件用于写入 |
ios::trunc | 如果该文件已经存在,其内容将在打开文件之前被截断,即把长度设为0 |
ofstream outfile;
outfile.open("file.dat", ios::out | ios::trunc);
关闭文件
close()函数也是fstream、ifstream、ofstream对象的成员
写入文件
在C++编程中我们使用流插入运算符(<<)向文件写入信息,这里使用的是ofstream或fstream对象
读取文件
在C++编程中我们使用流提取运算符(>>)从文件读取信息,这里使用的是ifstream或fstream对象
istream& getline(istream &is, string &str, char delim);
可读取整行,包括前导和嵌入的空格,并将其存储在字符串对象中。
槃 2020/2/12 20:55:26
istream &is
表示一个输入流,如cinstring &str
表示把从输入流读入的字符串存放在这个字符串中char delim
表示遇到这个字符停止读入,默认是’\n’
cin.getline(字符指针(char *), 字符个数(int N), 结束符(char));
一次读取多个字符(包括空格)一指定的地址为存放第一个读取字符的位置,依次向后存放,知道读满N-1个。
cin.ignore(a, ch)
从输入流cin提取字符,提取的字符被忽略,不被使用。计数值达到a或提取到字符ch,函数终止。
#include <iosstream>
#include <string>
using namespace std;
int main()
{
string name;
string city;
cout << "Please enter your name: ";
getline(cin, name); //cin是正在读取的输入流,第二个参数是string变量的名称
cout << "Enter the city you live in: ";
getline(cin, city);
cout << "Hello," << name << endl;
cout << "You live in" << city << endl;
return 0;
}
#include <iostream>
#include <fstream>
int main(int argc, char** argv)
{
char data[100];
ofstream outfile;
outfile.open("afile.dat");
cout << "Writing to the file" << endl;
cout << "Enter your name: "l
cin.getline(data, 100);
//向文件写入用户输入的数据
outfile << data << endl;
cout << "Enter your age: ";
cin >> data;
//ignore()函数会忽略掉之前读语句留下的多余字符
cin.ignore();
outfile << data << endl;
outfile.close();
//以读模式打开文件
ifstream infile;
infile.open("afile.dat");
//从文件读取数据,并显示它
infile >> data;
cout << data << endl;
inflie.close();
return 0;
}
16 类型转换构造函数、运算符,类成员指针
1 类型转换构造函数
2 类型转换运算符(类型转换函数)显式的类型转换运算符
有趣范例:类对象转换为函数指针3 类型转换的二义性问题
4 类成员函数指针对于普通成员函数
对于虚函数
对于静态成员函数5 类成员变量指针
对于普通成员变量
对于静态成员变量
类型转换构造函数
特点:
- 能够把其他类型转换成类类型
- 只有一个参数,该参数其实是待转换的数据类型
- 在函数中,要指定转换的方法
类型转换运算符:和类型转换构造函数能力相反
格式:operator type() const;
特点:
- 能够把类类型转换成其他类型,const是可选项,代表不能改变待转换对象内容
- type是想要转换成的某种类型
- 类型转换运算符没有形参(形参列表为),因为类型转换运算符都是隐式执行的,同时,也不能指定返回类型
- 必须定义为类的成员函数
class A {
public:
explicit operator int() const //explicit禁止了隐式的类型转换
{
return value;
}
int value;
};
A a;
a.value = 9;
int k = static_cast<int>(a) + 10; //显式类型转换
int k = a.operator int() + 10; //显式调用
类对象转化成函数指针
//定义一个函数指针类型
typedef void (*p) (int);
using p = void (*) (int);
类成员函数指针
对于普通成员函数/虚成员函数
格式:
类名::*函数指针变量名
来声明成员函数指针
&类名::成员函数名
来获取成员函数的地址
class Name {
public:
void func1(int getvalue) {}
};
int main()
{
void (Name::*pointname) (int);
pointname = &Name::func1; //成员函数,有类就有成员函数,而不是需要定义类对象才分配地址
Name person, *p;
(person.*pointname) (int i);
(p->*pointname) (int i);
}
对于静态类成员函数
静态类成员函数指针定义时*函数指针变量名
类成员变量指针
对于普通成员变量
并不是真正意义上的地址,指针指向的不是内存中某个地址,而是该成员变量与该类对象指针(对象首地址)之间的偏移量
class A {
int a;
};
int main() {
int A::*point = &A::a;
}
如果一个类中有虚函数,则对象中就会有一个指向虚函数表的指针,占4个字节。
而静态成员变量则属于类有自己的地址。定义时和普通指针相同无需加类名::