学习要点:
1 零初始化
d = T() 如果T是基本数据类型 则表示0
如果T是自定义数据类型 则使用无参构造函数
2 内部类型成员:
定义一个类型,受到访问权限影响,一般外部不能访问
3 内部成员指向动态内存时 需要重写析构 赋值 拷贝函数
4 指针的引用 Node*&p 表示指针本身 可以用来改变指针的指向.
一般在返回值和形参中出现这个
02list.h
#ifndef LIST_H
#define LIST_H
#include <iostream>
using namespace std;
typedef int T;
class List
{
//内部类型成员 受访问权限影响
struct Node
{
T data;
Node* next;
//一旦你写了构造函数 就不能再以前的方法初始化
// d = T() 零初始化 基本数据类型表示0 自定义数据类型表示无参构造函数
Node(const T& d = T() ):data(d),next(NULL)
{
}
};
Node* head; //头指针,用来保存头节点地址 。 有头节点的链表这里直接是Node对象
int len;
public:
List():head(NULL),len(0) { }
// //由于有指针成员指向动态内存时 需要重写三大函数 析构 赋值 拷贝构造
~List();
//拷贝赋值函数
List(const List &l);
//赋值函数
List& operator=(const List& l);
//函数的返回值如果是List型的 一定要是指针或者引用
// 当没有重写析构 赋值 拷贝构造函数, 如果以值类型返回,由于head都指向同一个地方 ,在临时变量析构时会释放链表中所有的空间
//
//个数 有n个数则返回n
int size() const;
//前插
void push_front(const T& d);
//后插
void push_back(const T&d);
//找链表中指向指定位置的指针
Node*& getptr(int pos);
//在任意位置插入
void insert(const T& d,int pos); //n个节点 有n+1个插入 pos 0~n
//遍历
void travel() const;
//按位置删除
void erase(int pos);
//按数值来得到指向节点的指针
int find(T d);
//按数值来删除节点 删除链表中所有的这个值
void remove(T d);
//修改节点
void set(int pos ,T d);
//判断是否为空
bool empty() const;
//返回头节点数据
T front() const ;
//返回尾节点数据
T back() const;
//链表反转
//思路1 指针反转 3个临时指针
void reverse();
//链表反转的另一种思路
//重新构造一个链表 原来的块头插进来 2个临时指针
void reverse2();
//从小到大排序
void sort();
//合并
void add(List& l);
//清空
void clear();
};
#endif
02list.cpp
#include "02list.h"
#include <iostream>
using namespace std;
// //由于有指针成员指向动态内存时 需要重写三大函数 析构 赋值 拷贝构造
List:: ~List()
{
clear();
}
//拷贝赋值函数
List:: List(const List &l)
{
head = NULL;
len = 0;
Node *p = l.head;
while(p != NULL)
{
push_back(p->data);
p = p->next;
}
}
//赋值函数
List& List::operator=(const List& l)
{
clear();
Node *p = l.head;
while(p != NULL)
{
push_back(p->data);
p = p->next;
}
return *this;
}
//函数的返回值如果是List型的 一定要是指针或者引用
// 当没有重写析构 赋值 拷贝构造函数, 如果以值类型返回,由于head都指向同一个地方 ,在临时变量析构时会释放链表中所有的空间
//
//个数 有n个数则返回n
int List:: size() const
{
return len;
}
//前插
void List:: push_front(const T& d)
{
/*
Node* p = new Node(d); //这里不可以用局部变量 因为局部变量会销毁
p->next = head;
head = p;
*/
insert(d,0);
}
//后插
void List:: push_back(const T&d)
{
insert(d,size()); //少用一次变量 出错几率就少一次 len 与 size()的区别
}
//找链表中指向指定位置的指针
// 返回引用类型 保证返回的是指针本身
// 如果不是引用 那就是值传递了。
//pos 从0~n 有n+1个位置 0~n都可以插值 位置0~n-1是用来删除和修改的
//因为我想在将来改变返回的这个指针的指向,所以写引用。
//若是值传递,我在调用函数后只能得到一个相同指向的指针,这样只能修改指向的对象的值
//,但却无法修改返回的这个指针的指向。
List::Node*& List:: getptr(int pos) // List::Node 内部类型
{
if(pos == 0) return head;
Node *p = head;
//1 return p->next
//2 p=p->next ; return p->next
for(int i =1; i < pos; i++)
{
p = p->next;
}
return p->next;
}
//在任意位置插入
//1在链表里找到指向这个位置的指针
//2 新节点指和找到的指针指向同一个地方
//3 指针指向新节点 ++len
void List:: insert(const T& d,int pos) //n个节点 有n+1个插入 pos 0~n
{
if(pos <0 || pos > size() ) pos = 0;
Node* &p = getptr(pos); //继续引用
Node *pn = new Node(d);
pn -> next = p;
p = pn;
len ++;
}
//遍历
void List:: travel() const
{
Node *p = head;
while(p != NULL)
{
cout << p->data << ' ';
p = p->next;
}
cout << endl;
}
//按位置删除
//插入可以有n+1个位置 删除有n个位置 0~n-1
//1找到指向这个节点的指针
//2 保存指针 为temp 指针指向这个节点的下一个节点
//3 释放temp的空间 --len
void List:: erase(int pos)
{
if(pos < 0 || pos > size() -1) //无效位置
{
return;
}
Node* &p = getptr(pos);
Node *temp = p;
p = p->next;
delete temp;
--len;
}
//按数值来得到指向节点的指针
int List:: find(T d)
{
Node *p = head;
int pos = 0;
while( p != NULL)
{
if( (p->data) == d ) return pos;
p = p->next;
pos ++;
}
return -1;
}
//按数值来删除节点 删除链表中所有的这个值
void List:: remove(T d)
{
int pos;
while( (pos=find(d)) != -1 )
{
erase(pos);
}
}
//修改节点
void List:: set(int pos ,T d)
{
if(pos < 0 || pos > size()-1) return;
getptr(pos) -> data = d;
}
bool List::empty() const
{
return head==NULL;
}
//返回头节点数据
T List::front() const
{
if(empty() ) throw "链表为空";
return head->data;
}
//返回尾节点数据
T List::back() const
{
if( empty() ) throw "链表为空";
Node *p = head;
while(p->next != NULL)
{
p = p->next;
}
return p->data;
}
//链表反转
//思路1 指针反转 3个临时指针
void List:: reverse()
{
//因为前面用了指针的引用,让我出现疑惑
//这里为什么不用引用呢?
//因为无论是函数形参还是返回值都存在值传递的问题,导致实际操作的
//是一个相同指向的临时指针 。
//而这个函数中不存在传递问题,是对指针的直接操作。
Node *p = head;
Node *p_next = NULL;
Node *p_pre =NULL;
while(p != NULL)
{
p_next = p->next;
p->next = p_pre;
p_pre = p;
p = p_next;
}
head = p_pre;
}
//链表反转的另一种思路
//重新构造一个链表 原来的块头插进来 2个临时指针
void List:: reverse2()
{
Node*p = head;
Node *q;
head = NULL;
while(p != NULL)
{
q = p->next;
p->next = head;
head = p;
p = q;
}
}
//从小到大排序
void List:: sort()
{
Node *p = head;
Node *q;
int temp;
while( (q=p->next) !=NULL)
{
if(p->data > q->data)
{
temp = p->data;
p->data = q->data;
q->data = temp;
p = head;
}
else
{
p = q;
}
}
}
//合并
void List:: add(List& l)
{
// getptr(size()) = l.head; 这样子是不安全的 对l的操作会影响本链表
Node *p = l.head;
while(p!=NULL)
{
push_back(p->data);
p=p->next;
}
}
//清空
void List:: clear()
{
while(head != NULL)
{
Node* p = head->next;
delete head;
head = p;
}
head = NULL;
len = 0;
}
main.cpp
#include <iostream>
#include "02list.h"
using namespace std;
int main()
{
List l;
cout << sizeof(l) << endl; // 大小 8
l.push_front(1);//头插
l.push_front(10);
l.push_front(100);
l.travel(); //100 10 1
l.insert(1000,0);
l.travel();//1000 100 10 1
l.push_back(10000); //1000 100 10 1 10000
l.travel();
l.insert(70,-1); // pos不正确 纠正为0
l.travel();//70 1000 100 10 1 10000
l.insert(7000,200); //pos不正确 纠正为0
l.travel();//7000 70 1000 100 10 1 10000
l.erase(100); //无效的位置 直接返回
l.travel();//7000 70 1000 100 10 1 10000
l.erase(5);//删除5号位置 即第六个位置
l.travel(); //7000 70 1000 100 10 10000
//插入重复的值
l.insert(999,2);
l.insert(999,5);
l.insert(999,6);
l.travel();//7000 70 999 1000 100 999 999 10 10000
l.remove(999); //按值删除
l.travel();//7000 70 1000 100 10 10000
//修改
l.set( l.find(10) , 9 ); //将10改成9
l.travel();//7000 70 1000 100 9 10000
l.set(0,8);//将第0个改成8
l.travel();//8 70 1000 100 9 10000
cout << boolalpha << l.empty() << endl; //false
cout << l.front() << ' ' << l.back() << endl;//8 10000
//cout << l.getptr(l.size() -1)->data << endl;
cout << "reverse : ";
l.reverse();
l.travel();
l.reverse2();
cout << "reverse2: ";
l.travel();
l.sort();
cout << "sort :";
l.travel();
l.reverse();
cout << "reverse";
l.travel();
//拷贝构造函数
List l2(l);
cout << "l2 travel:";
l2.travel();
List l3;
l3.push_back(1);
l3.push_back(2);
l3.push_back(3);
cout << "l3 travel :" <<endl;
l3.travel();
l2 = l3;
cout << "l2 travel :" <<endl;
l2.travel();
l2.add(l3);
cout << "l2 travel :" <<endl;
l2.travel();
cout << "size " << l2.size() << endl;
//清空
l3.clear();
while( !l.empty() )
{
l.erase(0);
}
cout << "size :" << l.size() << endl;
return 0;
}