1. 类的指针和引用
(1)指针和引用
在c语言中,指针是进行高效访问内存的手段,但是即便如此仍然涉及空间开辟和赋值
操作。在c++中,引入了比指针更加高效的传参方式,那就是引用,不需要任何的赋值
操作。
指针和引用作为高效使用内存的方式,在c++中,对于类和对象来说同样有着频繁的使用。
(2)引用并不能完全替代指针
实际上引用虽然比指针的效率更高,但是引用并不能完全的替代指针。引用有一个缺
点就是,引用只能初始化,不能赋值,但是指针不是的,指针可以通过重新赋值指针
任何需要的空间。
(3)指针在类中的应用
(1)传参
(2)类的成员
(1)函数指针
(2)普通类型指针
(2)对象指针
2. 类的访问权限修饰符
访问权限限制符有三种,public,private,protected,用于限制成员的访问权限,
是类封装不可取少的关键字,前面说过,类成员默认就是private修饰。
这三种符号在一起联合使用时,有九种组合,但实际上如果采用记忆的当时去记住这
九种组合并不可取。
如果想要完全弄清楚这三个符号作用的话,它们是有规律可循的,这个规律需要分为
有继承和没有继承的两种情况来展开讲述。
(1)没有继承时的情况
(1)public
在没有继承时,public表示来成员可以被外部访问,外部只需要通过./->/::进行访问。
(2)protected/private
在不考虑继承的情况下,private和protected权限的效果一样,表示隐藏成员,外
部不能直接通过./->/::对成员进行访问,只能通过公共成员函数接口访问隐藏成员。
private和protected到底有什么区别,涉及继承的时候才能完全的讲清楚。
类中的公共成员大多都是用作接口的成员函数,而类中几乎所有的数据成员都是使用
private或者protected进行隐藏的。
(2)有继承时的情况
当涉及继承时,这三个符号在修饰父类和子类的专属成员时(不包含子类继承自父类
的成员),其作用与上面所描述的一致。
子类分别有public/private/protected这三种继承父类的方式,哪些子类中继承自父
类的成员,它们在子类中的新权限会受到继承方式的影响。
public/private/protected这三种继承父类的方式具体是怎么影响继承的,我们讲到
继承时再做详细讲解
3. 动态分配对象空间
(1)静态对象空间
(2)局部对象空间
(3)动态分配空间
这个在前面章节实际上已经讲到过。
5. 类的嵌套
(1)内部嵌套定义对象成员
例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
using namespace std;
class Birthday
{
public:
Birthday(const string year="2000", const string month="12", \
const string day="12"):year(year), month(month), day(day) {}
string get_year() {
return year;
}
string get_month() {
return month;
}
string get_day() {
return day;
}
private:
string year;
string month;
string day;
};
class Student
{
public:
Student(const string name="name", int num=1, const string year="2008", \
const string month="1", const string day="1")
: name(name), num(num), birthday(year, month, day) { }
Birthday &get_birthday() {
return birthday;
}
private:
string name;
int num;
Birthday birthday;
};
int main(void)
{
Student stu1("zhangsan", 33);
cout<<"年:"<< stu1.get_birthday().get_year() << endl;
cout<<"月:"<< stu1.get_birthday().get_month() << endl;
cout<<"日:"<< stu1.get_birthday().get_day() << endl;
return 0;
}
例子分析:
本例子中,定义了两个类,一个是Student类,另一个是Birthday类,其中
在Student类中定义了一个Birthday类的成员。
注意本例子中对Student类中的Birthday成员的初始化方式。
(2)使用对象指针的方式
例子:
仍然使用上面的例子,但是需要做些改动。
Birthday birthday;
改为
Birthday *birthday;
Birthday的构造函数的初始化列表中的birthday(year, month, day)
改为
birthday(new Birthday(year, month, day))
将stu的定义和年月日的打印
Student stu1("zhangsan", 33);
cout<<"年:"<< stu1.get_birthday().get_year() << endl;
cout<<"月:"<< stu1.get_birthday().get_month() << endl;
cout<<"日:"<< stu1.get_birthday().get_day() << endl;
改为
Student *stu1 = new Student("zhangsan", 33);
cout<<"年:"<< stu1->get_birthday()->get_year() << endl;
cout<<"月:"<< stu1->get_birthday()->get_month() << endl;
cout<<"日:"<< stu1->get_birthday()->get_day() << endl;
最后别忘了加一个释放动态对象空间的语句。
delete stu1;
例子分析:
以上改动完成之后就实现了动态分配对象空间,动态对对象空间开辟自堆空间,
与c语言中利用malloc函数在堆中开辟空间没有任何区别。
实际上这个例子中存在一个程序隐患,因为stu1对象的birthday指向的对象空间并没
有被释放,这就会导致内存泄漏,是非常大的隐患。
(3)定义类的内部类
(1)什么是内部类
直接通过一个例子理解什么是内部类,还是以上面的例子为例,我们将
Birthday定义为Student的内部类。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
using namespace std;
class Student
{
private:
class Birthday
{
public:
Birthday(const string year="2000", const string month="12", \
const string day="12"):year(year), month(month), day(day) {}
string get_year() {
return year;
}
string get_month() {
return month;
}
string get_day() {
return day;
}
private:
string year;
string month;
string day;
};
public:
Student(const string name="name", int num=1, const string year="2008", \
const string month="1", const string day="1")
: name(name), num(num), birthday(new Birthday(year, month, day)) { }
Birthday *get_birthday() {
return birthday;
}
private:
string name;
int num;
Birthday *birthday;
};
int main(void)
{
Student *stu1 = new Student("zhangsan", 33);
cout<<"年:"<< stu1->get_birthday()->get_year() << endl;
cout<<"月:"<< stu1->get_birthday()->get_month() << endl;
cout<<"日:"<< stu1->get_birthday()->get_day() << endl;
delete stu1;
return 0;
}
例子分析:在本例子中,我们将Birthday类定义为了Student类的私有内部
类,其他的操作与前面例子并没有什么区别。
(2)内部类的意义
如果我们某写类需要使用属于自己的专属的内部子类时,我们就可以为其定
义内部类。
(3)隐藏的和公共的内部类
(1)隐藏内部类
我们定义内部类时,主要就是为了专享使用该内部类,因此我们一
般都会将其修饰为private或者protected,将它内部类隐藏起来,
外部是不能使用该类来实例化对象的。
上面所举的内部类的例子就是典型的隐藏内部类。
(2)公共的内部类
内部类也可以将其声明为public,如果声明为public的话,在外部
就可以访问该内部类。
如果我们将上面例子中的内部类改为public的话,在main函数中就可
以使用定义的内部类Birthday来实例化对象,定义形式如下:
Student::Birthday brthday;
如果将内部类声明为public的话,与直接在外部定义该类的使用效果
差不多,所以都会将其直接定义为外部类。我们如使用的内部类时,
很多情况下都会将其隐藏。
(4)通过友元实现专属内部类一样的效果
只用友元实现专属内部类的效果时,还是以上面的例子为例,实现步骤是:
(1)将内部Birthday类改为外部类
(2)将Birthday的所有成员全部定义为隐藏
(3)将Student类声明为Birthday类的友元
通过以上这三步,Birthday就变成了Student的专属类,因为Birthday的构造函数
是隐藏的,也没有其它实例化的接口,因此在外部无法实例化Birthday对象,
只有友元Student才能调用Birthday的构造函数实例化Birthday的对象。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
using namespace std;
class Birthday
{
Birthday(const string year="2000", const string month="12", \
const string day="12"):year(year), month(month), day(day) {}
string get_year() {
return year;
}
string get_month() {
return month;
}
string get_day() {
return day;
}
string year;
string month;
string day;
friend class Student;
};
class Student
{
public:
Student(const string name="name", int num=1, const string year="2008", \
const string month="1", const string day="1")
: name(name), num(num), birthday(new Birthday(year, month, day)) { }
Birthday *get_birthday() {
return birthday;
}
string get_year() {
return birthday->get_year();
}
string get_month() {
return birthday->get_month();
}
string get_day() {
return birthday->get_day();
}
private:
string name;
int num;
Birthday *birthday;
};
int main(void)
{
Student *stu1 = new Student("zhangsan", 33);
cout<<"年:"<< stu1->get_year() << endl;
cout<<"月:"<< stu1->get_month() << endl;
cout<<"日:"<< stu1->get_day() << endl;
delete stu1;
return 0;
}
例子分析:在本例子中,由于Birthday类的成员全部设置为了隐藏,因此
main函数中的如下语句是无法通过编译的,
cout<<"年:"<< stu1->get_birthday()->get_year() << endl;
cout<<"月:"<< stu1->get_birthday()->get_month() << endl;
cout<<"日:"<< stu1->get_birthday()->get_day() << endl;
这是因为,我们试图在外部访问Birthday中私有的getter函数,这显然是不
行的,由于我们将Student设置为了Birthday的友元,因此我们需要完全借助
Student类的对象才能访问Birthday中的成员,因此我们在Student中加了三
个访问Birthday获取器的代理成员函数:
string get_year() {
return birthday->get_year();
}
string get_month() {
return birthday->get_month();
}
string get_day() {
return birthday->get_day();
}
当然我们呢也可以直接将函数中的
return birthday->get_year();
return birthday->get_month();
return birthday->get_day();
改为
return birthday->year;
return birthday->month;
return birthday->day;
然后再main函数中通过调用Student中的三个代理访问Birthday成员的函数
才能将Birtday的成员数据打印出来,调用方式如下:
cout<<"年:"<< stu1->get_year() << endl;
cout<<"月:"<< stu1->get_month() << endl;
cout<<"日:"<< stu1->get_day() << endl;
(5)当类进行嵌套时,构造函数的执行的顺序
(1)构造函数中涉及初始化和赋值的问题
在上一章中我们强调过,在构造函数的{ }涉及的是赋值操作,但是
构造函数中的初始化列表涉及的是初始化操作的,它们的区别是,
初始化是由编译器一早就安排好的,效果就是一旦开辟空间就立即
向空间写值。而的赋值操作是先开辟空间然后再向空间赋值。
构造函数中赋值与初始化的区别将直接影响类在嵌套时,它们的
构造函数被调用的顺序。
(2)例子
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
using namespace std;
class A
{
public:
A(int a=1){
cout<<"A的构造函数"<<endl;
}
};
class B
{
A *a;
public:
B(int b=1):a(new A(1)) {
cout<<"B的构造函数"<<endl;
}
};
class C
{
B *b;
public:
C(int c=1) {
cout<<"C的构造函数"<<endl;
b = new B(1);
}
};
int main(void)
{
C c(1);
return 0;
}
运行结果:
C的构造函数
A的构造函数
B的构造函数
例子分析:
例子中C类包含了B类的对象,B类包含了A类的对象,但是为什么构造函数
被执行的顺序是CAB呢?
执行的顺序如下:
当在main函数中执行C c(1); 回到main函数
| A
| |
V |
C类的构造函数被调用 |
执行 cout<<"C的构造函数"<<endl; |
执行 b = new B(1); C类的构造函数执行完毕
| A
V |
B类的构造函数被调用 |
利用a(new A(1))初始化对象a 执行 cout<<"B的构造函数"<<endl;
| A
V |
A类构造函数被调用 |
执行 cout<<"A的构造函数"<<endl; |
A构造函数执行结束--------------------------------------->
从上面的分析看出,构造函数的初始化列表优先于{ }中的的赋值语句的执行。
构造函数被调用执行是一个递归的过程。
4. 重要的析构函数
(1)前面动态分配对象空间例子中的隐患
在前面动态分配对象空间的例子中,其实是有问题的,那就是Student中Birthday指
针指向的Birthday类对象,该对象空间分配于堆空间,因此需要释放,但是我们并
没有释放它,这个问题将会留给后面需要讲到的析构函数去解决。
(2)析构函数
(1)析构函数的作用
每个对象空间被释放时,都会调用析构函数,析构函数的目的主要是为了
实现对对象的扫尾操作。
对于类中的分配于堆空间的类类型的成员对象来说,其空间是不会自动被释
放的,因为这是程序员的事情,而这些释放空间的操作往往就放在析构函数
中来做。
(2)默认的析构函数
实际上,如果我们自己不定义析构函数的话,每个类都有一个默认的析构
函数,这个析构函数是在编译时提供的,这个析构函数是不可见的。
(3)析构函数的格式
与构造函数几乎相同,只是需要在函数名亲前面加~,析构函数没有形参
,但是实际上它会得到一个默认参数,就是this指针,因为析构函数需要
操作成员,这些成员需要使用this访问,只是一般情况下,并不会显式使
用this访问成员。
析构函数定义格式如下(以前面的Student类为例):
~Student() {
......//默认析构函数没有内容
}
或者
Student::~Student() {
......//默认析构函数没有内容
}
(4)调用析构函数
(1)析构函数什么时候被调用
当对象空间被释放时,析构函数将会被调用
(1)静态对象:当整个程序结束时,分配于静态空间的对象才会被释放
(1)全局变量
(2)静态局部变量
(2)自动局部对象
函数调用结束后,自动局部对象的空间即会被释放
(3)自动分配的对象
自动分配对象空间的释放
(1)程序结束后,一定会释放
(2)主动调用delete释放
而这的区别是delete释放空间时会调用析构函数,但是程序结束
后释放自动分配对象空间的方式则不会调用析构函数。
实际开发中,很多程序基本都会长时间运行,甚至说永远运行,
在这一类的程序中,如果存在大量的自动分对象的话,一定要主
动释放,否则严重的内存泄漏会直接导致程序崩溃。
(2)举例
还是前面的学生例子,但是将其main函数的内容改成如下形式。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
using namespace std;
class Birthday
{
Birthday(const string year="2000", const string month="12", \
const string day="12"):year(year), month(month), day(day) {}
string get_year() {
return year;
}
string get_month() {
return month;
}
string get_day() {
return day;
}
string year;
string month;
string day;
friend class Student;
};
class Student
{
public:
Student(const string name="name", int num=1, const string year="2008", \
const string month="1", const string day="1")
: name(name), num(num), birthday(new Birthday(year, month, day)) { }
~Student() {
cout << "Student的析构函数" << endl;
delete birthday;
}
Birthday *get_birthday() {
return birthday;
}
string get_year() {
return birthday->get_year();
}
string get_month() {
return birthday->get_month();
}
string get_day() {
return birthday->get_day();
}
private:
string name;
int num;
Birthday *birthday;
};
void fun() {
Student *stu1 = new Student("zhangsan", 33);
Student stu2("zhangsan", 33);
static Student stu3("zhangsan", 33);
cout<<"年:"<< stu1->get_year() << endl;
cout<<"月:"<< stu1->get_month() << endl;
cout<<"日:"<< stu1->get_day() << endl;
delete stu1;
}
Student stu4("zhangsan", 33);
int main(void)
{
fun();
// while(1);
return 0;
}
例子分析:
fun函数中的stu1是自动分配的,需要delete才能释放,并会调用析构函数
fun函数中的stu2是fun函数的局部变量,当函数运行结束后会自动释放,并会调用析构函数
fun函数中stu3是静态局部变量,只有当整个程序运行结束之后才会释放,并会调用析构函数
全局变量stu4是静态变量,同样的,也只有当整个程序运行结束之后才会释放,并会调用析构函数
对他们进行空间释放并调用析构函数的顺序是:
先是stu2
再是stu1
最后程序结束时才是stu3和stu4
(5)类有嵌套时,析构函数的调用顺序
(1)当类嵌套时析构函数的调用顺序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
using namespace std;
class A
{
public:
A(int a=1){ }
~A() {
cout<<"A的析构函数"<<endl;
}
};
class B
{
A *a;
public:
B(int b=1):a(new A(1)) { }
~B() {
cout<<"B的析构函数"<<endl;
delete a;
}
};
class C
{
B b;
public:
C(int c=1):b(1){
}
~C() {
cout<<"C的析构函数"<<endl;
}
};
int main(void)
{
C *c1 = new C(1);
delete c1;
cout<<"释放C1的堆空间并调用析构函数\n"<<endl;
C c2(1);
cout<<"程序结束,C1的静态空间被释放并调用析构函数"<<endl;
return 0;
}
运行结果:
释放C1的堆空间并调用析构函数
C的析构函数
B的析构函数
A的析构函数
程序结束,C1的静态空间被释放并调用析构函数
C的析构函数
B的析构函数
A的析构函数
例子分析:
从例子的运行结果来看,很容易发现,析构函数的调用是从最外层的对象开始的。
但是如果将B类的析构函数的delete a代码注释掉,你会发现A的析构函数没有被
调用,也就说明B类中的A类指针指向的对象成员的空间没有被释放。
因此可以看出,对于在类中有自动分配的对象成员时,在析构函数中显式调用
delete释放对内存。
(5)析构函数的权限
正常情况下需要将析构函数设置为public,否者将无法编译将无法通过。
但是有一种情况是可以将析构声明为隐藏的,比如A类的所有成员都是隐藏的,
包括析构函数也是隐藏的,但是另一个B类是A类的友元的时候,A类隐藏的析构
函数也可以被调用,
例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
using namespace std;
class A
{
A(int a=1){ }
~A() {
cout<<"A的析构函数"<<endl;
}
};
class B
{
A *a;
public:
B(int b=1):a(new A(1)) { }
~B() {
cout<<"B的析构函数"<<endl;
delete a;
}
};
int main(void)
{
B b(1);
return 0;
}
编译结果,编译错误:
a.cpp:11: error: ‘A::A(int)’ is private
a.cpp:22: error: within this context
a.cpp: In destructor ‘B::~B()’:
a.cpp:13: error: ‘A::~A()’ is private
a.cpp:26: error: within this context
编译错误提示,A的构造函数和析构函数都是隐藏的,因此B类将不能调用
A类的构造函数进行进行初始化成员,也不能调用析构函数析构操作。
面对这个情况有两个解决办法:
办法1:将构造函数和析构函数都声明为public
办法2:将B类声明为A类的友元,即便A类的构造函数和析构函数是隐藏的
也没有关系。
class A
{
A(int a=1){ }
~A() {
cout<<"A的析构函数"<<endl;
}
friend class B;
};
6. 再次探讨副本构造函数(拷贝构造函数)的重要性
上一章节提到,当从A对象复制出完全相同的B对象时(比如对象的值传递),就会
调用副本构造函数进行复制。
但是默认的拷贝函数进行的只是浅拷贝操作,当类对象包含堆堆空间的成员时,默
认的浅拷贝会带来隐患,需要进行深拷贝,这就需要我们重写副本构造函数。
特别是在类对象包含自动分配的成员对象时,重写拷贝构造函数几乎是必须的操作。
例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
using namespace std;
class Province
{
public:
string name;
Province(const string name=" "):name(name) { }
~Province() { }
};
class Country
{
public:
Province *province;
Country(const string name=" "):province(new Province(name)) { }
~Country() {
delete province;
}
};
int main(void)
{
Country c1("shandong");
Country c2 = c1;
cout<<"通过c1打印省名:"<<(c1.province)->name<<endl;
cout<<"通过c2打印省名:"<<(c2.province)->name<<endl;
c2.province->name = "hebei";
cout<<"\n通过c2改变省名后"<<endl;
cout<<"通过c1打印省名:"<<(c1.province)->name<<endl;
cout<<"通过c2打印省名:"<<(c2.province)->name<<endl;
return 0;
}
运行结果:
通过c1打印省名:shandong
通过c2打印省名:shandong
通过c2改变省名后
通过c1打印省名:hebei
通过c2打印省名:hebei
段错误
例子分析:
本例子使用的是默认副本构造函数,因此将c1赋值给c2时,是浅拷贝操作,所以你会
发现在通过c2修改了省份名字后,c1打印的省份名字也修改了。
实际上后面出现的段错误也是由浅拷贝引起的,因为c1和c2的pronvice成员指向的是
同一个Province对象,因此在c1和c2空间被释放时,province成员指向自动分配的对
象会被释放两次,因此造成段错误。
修改方法:
在Country中加入如下自定义的副本构造函数,把将pronvice指向新的自动分配的对象
空间。
Country(const Country &country) {
this->province = new Province();
this->province->name = country.province->name;
}
7. 利用c++知识实现学生链表案例
(1)c++实现链表的方式
很多数据结构的书都会讲解以c语言实现的链表,数据节点使用结构体实现,很明显也可以
利用c++来实现来链表,数据节点则使用类对象代替结构体变量。
(2)c++中我们并不需要自己实现各种数据结构
当然在c++中我们实际上没有必要自己自己实现链表,因为在c++中的STL容器已经帮我们实
现了非常强大的链表数据结构,只需要学会使用即可。
(3)自己实现c++链表的意义
(1)有利于理解对象和结构体的异同
(1)通过c和c++所实现链表的对比,也可以加深我们对于链表的理解
(2)同时也可以借机了解c++中STL容器的list是如何实现的
(4)这里写的c++链表与STL容器的list的区别
这里实现的链表例子与STL的list最大区别在于,这里没有使用类模板(泛型),因此这里自
定义实现的链表只能用来存放确定类型的数据,无法存放任意类型的数据。
因此如果想实现一个完美的链表的话,就必须引入泛型(函数模板/类模板).
(5)理解c中为什么没有统一的数据结构的实习那
因为c中缺乏泛型机制,所以c中没有定义统一的数据结构,在c中常见的情况就是针对不同的
结构体类型,总是要实现不同的链表等的数据结构图。
因此我们才说,数据结构这么课的内容在c语言中的使用是最为频繁的。
(6)例子
如果涉及io操作,这里沿用c语言提供的io流的操作函数。
写一个studata.h,定义存放数据类。
studata.h
#ifndef H_STUDATA_H
#define H_STUDATA_H
using namespace std;
class Studata
{
public:
int num;
string name;
Studata(int num=0, const string name=" "):num(num), name(name) {}
int get_num() const{ return num; }
string get_name() const{ return name; }
void set_num(int num) { this->num = num; }
void set_name(string name) { this->name = name; }
};
#endif
写一个stunode.h,节点类定义。
stunode.h
#ifndef H_STUNODE_H
#define H_STUNODE_H
#include "studata.h"
using namespace std;
class Stunode
{
Studata *studata;
Stunode *prev;
Stunode *next;
public:
Stunode(int num=0, const string name=" ", Stunode *prev=NULL, \
Stunode *next=NULL):studata(new Studata(num, name)) {}
Stunode(const Studata &studata) {
this->studata = new Studata();
this->studata->set_num(studata.get_num());
this->studata->set_name(studata.get_name());
prev = next = NULL;
}
~Stunode() {
delete studata;
}
/*获取器*/
Stunode *get_prev() const {return prev;}
Stunode *get_next() const {return next;}
Studata *get_studata() const {return studata;}
/*设置器*/
void set_prev(Stunode *stunode) {this->prev = stunode;}
void set_next(Stunode *stunode) {this->next = stunode;}
void set_studata(Studata *studata) {this->studata = studata;}
};
#endif
写一个dlist.h,定义封装整个链表操作的类。
dlist.h
#ifndef H_DLIST_H
#define H_DLIST_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
#include <errno.h>
#include "stunode.h"
using namespace std;
class Dlist
{
Stunode *hp;
Stunode *current;
Stunode *tmp;
public:
Dlist() {
hp = current = new Stunode();//空头节点
hp->set_prev(hp);
hp->set_next(hp);
tmp = NULL;
}
void err_fun(string filename, int line, string funname, int err_no) {
cerr<<filename<<" "<<line<<" "<<funname<<" fail"<<":"<<strerror(err_no)<<endl;
exit(-1);
}
void init_Dlist(string filename);
void insert_tail();
void insert_head();
void display();
Studata find(int index);
//void operator[](int i);
};
#endif
写一个dlist.cpp,操作链表的函数都定义在里面。
dlist.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
#include <errno.h>
#include "dlist.h"
using namespace std;
Studata Dlist::find(int index)
{
int i=0;
for(current=hp->get_next(); ; current=current->get_next(), i++)
{
if(current == hp) return (Studata)0;
else if(i == index) return *(current->get_studata());
//cout<<current->get_studata()->get_num()<<"\t"<<current->get_studata()->get_name()<<endl;
}
}
void Dlist::display()
{
for(current=hp->get_next(); hp!=current; current=current->get_next())
{
cout<<current->get_studata()->get_num()<<"\t"<<current->get_studata()->get_name()<<endl;
}
}
void Dlist::insert_tail()
{
hp->get_prev()->set_next(tmp);
tmp->set_prev(hp->get_prev());
hp->set_prev(tmp);
tmp->set_next(hp);
}
void Dlist::insert_head()
{
hp->get_next()->set_prev(tmp);
tmp->set_next(hp->get_next());
hp->set_next(tmp);
tmp->set_prev(hp);
}
void Dlist::init_Dlist(string filename)
{
FILE *fp = NULL;
fp = fopen(filename.c_str(), "r");
if(NULL == fp) err_fun(__FILE__, __LINE__, "fopen", errno);
while(1)
{
int num;
char name[40];
fscanf(fp, "%d %s\n", &num, name);
//printf("%d %s\n", num, name);
tmp = new Stunode(num, name);
insert_tail();
if(1 == feof(fp)) break;
}
}
main.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
#include <errno.h>
#include "dlist.h"
using namespace std;
int main(void)
{
Studata data;
Dlist dlist;
dlist.init_Dlist("./stu.txt");
dlist.display();
data = dlist.find(2);
cout << data.get_num()<<data.get_name() <<endl;
return 0;
}
/* 存放学生数据的文件 */
stu.txt
1 aaa
4 fff
3 sss
5 www
2 vvv
网友评论