1.C++与C语言的区别
1.C语言头文件<XXX.h>,C++后缀不带.h
2.C++中有bool类型(整型) / 单独占一个字节 / 取值true(1)false(0)
2.引用
2.1 特点
1.引用实体与引用类型必须为同一种类型
2.引用在定义时必须初始化并且不能被初始化为NULL
3.不可以改变引用关系
注:被const修饰的变量其引用也要被const修饰
2.2 参数加以引用的原因
1.可以在函数内部对此参数进行修改
2.提高函数调用和运行效率
以引用作为返回值的好处:在内存中不产生被返回值的副本
2.3 引用和指针的区别
1.引用在定义时必须初始化并且不能被初始化为NULL,指针没有要求
2.引用不能改变引用关系,指针随意
3.存在多级指针(**p)没有多级引用
4.引用的++和指针的++表达的含义不同,指针++(自增)是指针上的计算,引用++单纯+1
5.指针用sizeof计算的大小结果不同,指针在32位内存下是4个字节,64位内存下是8字节,引用的sizeof结果以引用类型大小决定
3.重载函数
C++中在同一个作用域下某个函数/运算符可以指到多个定义
在同一个作用域内可以声明多个功能类似的同名函数,但这些同名函数的形参的个数,类型,顺序必须不同,不能仅通过函数返回值类型不同来重载函数
#include<iostream>
#include<vector>
#include<string>
using namespace std;
//函数名相同,但参数的类型、数量、顺序不同
void add(int a,int b)
{
cout << "int a int b" << endl;
}
void add(double a,int b)
{
cout << "double a int b" << endl;
}
int main()
{
add(1, 2);//输出int a int b
add(0.1, 5);//输出 double a int b
return 0;
}
4.函数参数默认值
函数声明/定义时可以直接给形参赋值(必须从右向左依次赋值)若在调用函数时未给参数传值,会使用给定的默认值
#include<iostream>
#include<vector>
#include<string>
using namespace std;
//函数名相同,但参数的类型、数量、顺序不同
void add(int a ,int b = 2)
{
cout << a << b << endl;
}
void add1(double a = 0.1,int b)//报错,应从右向左赋值
{
cout << "double a int b" << endl;
}
int main()
{
add(1);//输出1 2
return 0;
}
5.vector数组
1.常用函数
back( ) 返回最末一个元素
swap( , ) 交换两个元素的值
push_back( ) 在最后添加一个元素
pop_back( ) 移除最后一个元素
front( ) 返回第一个元素
empty( ) 判断Vector是否为空 (true为空,false非空)
clear( ) 清空所有元素
2.迭代器
#include<iostream>
#include<vector>
#include<string>
using namespace std;
int main()
{
vector<int> array;
int i = 0;
for ( i = 0; i < 10 ; i++)
{
array.push_back(i);
}
for (vector<int>::iterator it = array.begin();it != array.end();it++)
//interator 迭代器
{
cout << *it << endl;
}
return 0;
}
6.面向对象三大特征
1.封装:将属性(成员变量)和操作(成员函数)结合为一个独立的整体。
2.继承:子类继承父类的特征和行为,使得子类对象具有父类的实例域和方法,或者子类从父类继承方法,使得子类具有父亲相同的行为
3.多态:多态是同一个行为具有多个不同的表现形式或形态的能力
声明和定义的区别
声明是告诉编译器变量或函数的类型和名字,不会为变量分配空间
定义就是对这个变量或函数进行内存分配和初始化,需要分配空间,同一个变量可以被声明多次,但是只能被定义一次
构造函数
没有返回值 语法: 类名(){ }
函数名称与类名相同
构造函数可以有参数,可以重载
编译器在创建对象的时候会自动调用构造函数
初始化参数列表
只能在构造函数里使用该语法,可以给所有成员设置初始化参数,const类型和引用类型必须在初始化参数列表中初始化。成员的构造顺序和在初始化参数列表中的顺序无关,与在类中声明顺序有关
析构函数
析构函数是作用域对象销毁工作,清空对象内部指针指向的堆区内存,析构函数在释放对象的时候自动调用,栈区对象自动释放。
语法: 无返回值 ~类名( ) { }
函数名称与类名相同,但是前面要有~
析构函数不可以有参数,因此不能发生重载
编译器在对象销毁前会自动调用析构函数,不需要手动调用
函数重写(覆盖)
子类重新定义父类中有相同名称,返回值和参数的虚函数,主要在继承关系中出现
基本条件:
重写的函数和被重写的函数都必须为virtual函数,并分别位于基类和派生类中
重写的函数和被重写的函数,返回值,函数名和函数参数必须完全一致
函数隐藏
在父类和子类中,函数名相同,参数不同,无论父类中的童工函数是否含有virtual关键字,都是隐藏
函数隐藏和函数重写的区别:在父类和子类中,函数名相同,参数相同,父类中的同名函数没有virtual关键字为隐藏
在子类中只要和父类函数名字相同不是重写,一定是函数隐藏
多态 & 虚函数表
多态按字面意思就是多种形态,当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。C++多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
·静态多态
函数重载(运算符重载),模板属于静态多态,在编译期间就能确定的多态
·动态多态
父类指针或引用指向子类对象,通过指针或引用调用子类重写的虚函数,在运行期间才能确定具体调用哪个函数,是动态多态
·启动动态多态的条件
有继承关系,子类重写父类虚函数并且父类指针调用子类重写的虚函数
#include<iostream>
using namespace std;
//重写:子类和父类中,同名且同形参的虚函数能够发生重写
//子类重写父类的虚函数(virtual)
class A
{
public:
int a;
int b;
int c;
const int d = 1;
virtual void work()
{
cout << "A work()" << endl;
}
void fun()
{
cout << "A fun()" << endl;
}
};
class B : public A
{
public:
virtual void work()
{
cout << "B重写了父类A的work函数" << endl; //函数重写
}
void fun()
{
cout << "B fun()" << endl; //函数隐藏
}
};
class C : public B
{
public:
void work()
{
cout << "C重写了父类B的work函数" << endl; //函数重写
}
void fun()
{
cout << "C fun" << endl; //函数隐藏
}
};
int main()
{
A* a = new C();
a->work(); //函数重写 生成"C重写了父类B的work函数"
a->fun(); //函数隐藏 生成"A fun()"
A b;
b.fun(); //生成"A fun()"
b.work(); //生成"A work()"
B* c = new C();
c->fun(); //生成"B fun()"
c->work(); //函数重写 生成"C重写了父类B的work函数"
return 0;
}
多态的实现
为了实现C++的多态,C++使用了一种动态绑定的技术。这个技术的核心是虚函数表,下面介绍虚函数表是如何实现动态绑定的。
类的虚函数表
每个包含了虚函数的类都包含一个虚表(存放虚函数指针的数组)
当一个类(B)继承另一个类(A)时,类B会继承类A的函数调用权,所以如果一个基类包含了虚函数,那么其继承类也可以调用这些虚函数,换句话说,一个类继承了包含虚函数的基类,那么这个类也拥有自己的虚表
以下代码,类A包含了虚函数,故A拥有一个虚表,类B继承类A,同时继承了类A的函数调用权,所以类B也有一个虚表
class A {
//由于类A中有虚函数,所以类A拥有一个虚表
public:
virtual void vfunc1();
virtual void vfunc2();
void func1();
void func2();
private:
int m, n;
};
class B :public A {
//此时类B也拥有自己的虚表
};
虚表是一个存放指针的数组,其内的元素是虚函数的指针,每个元素对应一个虚函数的函数指针,需要指出的是:普通的函数即非虚函数,其调用并不需要经过虚表,所以虚表的元素并不包括普通函数的函数指针。
虚表内的条目,即虚函数指针的赋值发生在编译器的编译阶段,也就是说在代码的编译阶段,虚表就可以构造出来了
虚表指针
虚表是属于类的,而不是属于某个具体的对象,一个类只需要一个虚表即可,同一个类的所有对象都使用同一个虚表。
为了指定对象的虚表,对象内部包含一个虚表的指针来指向自己所使用的虚表,为了让每个包含虚表的类的对象都有一个虚表指针,编译器在类中添加了一个指针,*_vptr,用来指向虚表。这样,当类的对象在创建时便拥有了这个指针,且这个指针的值会自动设置为指向类的虚表。
验证_vptr的方法就是先求一个普通类占的字节大小sizeof(),然后将类中某一个函数前加virtual关键字变为虚函数,再求该类占的字节数大小sizeof(),会发现增加了4个字节,这就验证了vptr的存在
动态绑定
动态联编(动态绑定)是指编译程序在编译阶段并不能确切地知道将要调用的函数,只有在程序执行时才能确定将要调用的函数,为此要确切的知道将要调用的函数,要求联编工作在程序运行时进行,这种在程序运行时进行的联编工作成为动态联编。
C++规定:动态联编是在虚函数的支持下实现的
动态联编必须包括以下方面:
1、成员函数必须声明为virtual
2、如果基类中声明了为虚函数,则派生类中不必再声明
调用方式:
通过对象的指针或引用调用成员函数,或通过成员函数调用,繁殖就无法实现动态联编
特点:
灵活、问题抽象性和问题的易维护性
#include<iostream>
using namespace std;
class A {
public:
virtual void vfunc1();
virtual void vfunc2();
void func1();
void func2();
private:
int m, n;
};
class B :public A {
public:
virtual void vfunc1();//重写
void func1();
private:
int a;
};
class C : public B {
public:
virtual void vfunc2();
void func2();
private:
int m,b;
};
图为类A,类B,类C的对象模型
由于这三个类都有虚函数,故编译器为每个类都创建了一个虚表,即类A的虚函数表 (A vtbl),类B的虚函数表(B vtbl),类C的虚表(C vtbl)。类A,类B,类C的对象都拥有一个虚表指针*_vpter,用来指向自己所属类的虚表。
类A包括两个虚函数,故A vtbl包含两个指针,分别指向A::vfunc1()和A::vfunc2
类B继承于类A,故类B可以调用类A的函数,但由于类B重写了B::vfunc1(),故B vtbl的两个指针分别指向B::vfunc1()和A::vfunc2()
类C继承于类B,故类C可以调用类B的函数,但由于类C重写了C::vfunc2()函数,故C vtbl的两个指针分别指向B::vfunc1()(指向继承的最近的一个类的函数)和C::vfunc2()
虚表中的指针会指向其继承的最近的一个类的虚函数
非虚函数的调用不经过虚表,所以不需要虚表中的指针指向这些函数
创建链表:
main.cpp
#include"MyList.h"
int main()
{
vector<int> vec = { 1,2,3,4,5 };
return 0;
}
MyList.h
#pragma once
#include<iostream>
#include<vector>
using namespace std;
struct ListNode
{
//数据域
int val;
//指针域
ListNode* next;
ListNode(int val)
{
this->val = val;
next = nullptr;
}
};
class MyList
{
ListNode* Head = nullptr;
ListNode* Tail = nullptr;
public:
MyList();
MyList(vector<int> vec);
};
MyList.cpp
#include "MyList.h"
MyList::MyList()
{
}
MyList::MyList(vector<int> vec)
{
if (vec.size() == 0)
{
return;
}
Head = new ListNode(vec[0]);
Tail = Head;
for (int i = 1; i < vec.size(); i++)
{
ListNode* node = new ListNode(vec[i]);
Tail->next = Tail;
}
}
——————————————————补——充——————————————————-——
栈
#include<iostream>
#include<stack>
using namespace std;
int main()
{
stack<int> stk;
for (int i = 1;i < 5;i++)
{
stk.push(i);
//在栈顶部增加元素
}
while (!stk.empty())
{
cout << stk.top() << endl;
//访问栈顶元素
stk.pop();
//移除栈顶元素
}
return 0;
}
——————————————————补——充——————————————————-——
树
数是一种数据结构,它是由n(n>=1)个有限节点组成的一个具有层次关系的集合
特点
每个节点有零个或多个子节点
没有父节点的结点成为根节点
每个非根节点有且只有一个父结点
除了根节点外,每个子节点可以分为多个不相交的子树
树的术语
结点的度:
结点所拥有子树的数目
叶子:
度为零的结点
分支结点:
度不为零的结点
树的度:
树中结点的最大的度
层次:
根结点的层数为1,其余结点的层次等于该结点的父亲结点的层次+1
树的高度:
树中结点的最大层次
无序树:
如果树中结点的各子节点之间的次序是不重要的,可以交换位置
有序树:
如果树中结点的各子树之间的次序是重要的,不可以交换位置
森林:
0个或多个不相交的树组成,对森林加上一个根,森林即成为树,删去根,树即为森林
二叉树的性质:
二叉树第i层上的结点数最多为2的i-1次方个
深度为k的二叉树最多有2的k次方-1个结点
包含n个结点的二叉树的高度至少为log2(n+1)
在任意一颗二叉树中,若叶子结点的个数为n0,度为2的节点数为n2,则n0=n2+1
二叉搜索树
二叉搜素树又叫二叉查找树,其中每个节点都有一个键标识该节点唯一,并且每个键大于左子树上任意节点的键,小于右子树上任意节点的键
特点:
若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值
任意结点的右子树不空,则右子树上所有结点的值均大于它的根结点的值
任意结点的左、右子树分别为二叉查找树
没有键值相等的结点
__________________________________________________________________
树的创建:
main.cpp
#include<iostream>
#include<vector>
#include"Tree.h"
using namespace std;
int main()
{
vector<int> res = { 7,4,8,6,4,1,3 };
Tree t(res);
t.Level(t.get_root());
return 0;
}
Tree.cpp
#include"Tree.h"
Tree::Tree(vector<int> &vec)
{
root = new Node(vec[0]);
for (int i = 1;i<vec.size();i++)
{
Node* p = root;
while (1)
{
if (vec[i] < p->val)
{
if (p->left == nullptr)
{
p->left = new Node(vec[i]);
break;
}
p = p->left;
}
if (vec[i] > p->val)
{
if (p->right == nullptr)
{
p->right = new Node(vec[i]);
break;
}
p = p->right;
}
if (vec[i] == p->val)
{
cout << "出现重复值,跳过此数" << endl;
break;
}
}
}
}
Tree.h
#pragma once
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
struct Node
{
int val;
Node* left = nullptr;
Node* right = nullptr;
Node(int val)
{
this->val = val;
}
};
class Tree
{
private:
Node* root = nullptr;
public:
Tree(vector<int>& vec);
Node* get_root() { return root; }
void Level(Node* root)
{
queue<Node*> que;//创建队列
que.push(root);
while (!que.empty()) //如果队列为空则返回1,不为空则返回0
{
int n = que.size();
for (int i = 0; i < n; i++)
{
Node* front = que.front(); //
cout << front->val << " ";
que.pop();
if (front->left) que.push(front->left);
if (front->right) que.push(front->right);
}
cout << endl;
}
}
};
输出结果:
树的遍历
先序遍历:
void Tree::PreOrder() //先序遍历
{
stack<Node*> stk;
stk.push(root);
while (!stk.empty())
{
Node* temp = stk.top();
stk.pop();
cout << temp->val << " ";
if (temp->right) stk.push(temp->right);
if (temp->left) stk.push(temp->left);
}
}
中序遍历:
void Tree::InOrder()//中序遍历
{
stack<Node*> stk;
Node* cur = root;
while (cur || !stk.empty())
{
while (cur)
{
stk.push(cur);
cur = cur->left;
}
Node* temp = stk.top();
stk.pop();
cout << temp->val << " ";
cur = temp->right;
}
}
后序遍历:
void Tree::PosOrder()//后序遍历
{
stack<Node*> s1, s2;
s1.push(root);
while (s1.size())
{
Node* temp = s1.top();
s1.pop();
s2.push(temp);
if (temp->left) s1.push(temp->left);
if (temp->right) s1.push(temp->right);
}
while (s2.size())
{
cout << s2.top()->val << " ";
s2.pop();
}
}
层级遍历:
void Level(Node* root)
{
queue<Node*> que;//创建队列
que.push(root);
while (!que.empty()) //如果队列为空则返回1,不为空则返回0
{
int n = que.size();
for (int i = 0; i < n; i++)
{
Node* front = que.front(); //
cout << front->val << " ";
que.pop();
if (front->left) que.push(front->left);
if (front->right) que.push(front->right);
}
cout << endl;
}
}
排序
归并排序:
#include<iostream>
#include<vector>
using namespace std;
void Mery(vector<int>& vec, int L, int mid, int R)
{
vector<int> temp(R - L + 1);
int i = L, j = mid + 1, index = 0;
while (i <= mid && j <= R)
{
if (vec[i] <= vec[j])
{
temp[index++] = vec[i++];
}
else
{
temp[index++] = vec[j++];
}
}
while (i <= mid)
{
temp[index++] = vec[i++];
}
while (j <= R)
{
temp[index++] = vec[j++];
}
index = L;
for (int i = 0; i < (R - L + 1); i++)
{
vec[index++] = temp[i];
}
}
void Merg_Sort(vector<int> &vec,int L, int R)
{
if (L >= R) return;
int mid = (R - L) / 2 + L;
Merg_Sort(vec, L, mid);
Merg_Sort(vec, mid + 1, R);
Mery(vec, L, mid, R);
}
int main()
{
vector<int> vec = { 3,4,5,1,7,8,6,4 };
Merg_Sort(vec, 0, vec.size() - 1);
return 0;
}
快排:
#include<iostream>
#include<vector>
using namespace std;
pair<int, int> Quick(vector<int>& vec, int L, int R)
{
int temp = vec[L];
int i = L - 1;
int j = R + 1;
int index = L;
while (index < j)
{
if (vec[index] == temp)
{
index++;
}
else if (vec[index] > temp)
{
swap(vec[--j], vec[index]);
}
else
{
swap(vec[++i], vec[index++]);
}
}
return make_pair(i, j);
}
void Quick_Sort(vector<int>& vec, int L, int R)
{
if (L >= R) return;
pair<int, int> p = Quick(vec, L, R);
Quick_Sort(vec, L, p.first);
Quick_Sort(vec, p.second, R);
}
int main()
{
vector<int> vec = { 5,4,2,5,6,8,1,0,9 };
Quick_Sort(vec, 0, vec.size() - 1);
for (auto it : vec)
{
cout << it << " ";
}
return 0;
}
冒泡
#include<iostream>
#include<vector>
#include<string>
using namespace std;
void Maopao_sort(vector<int>& nums)
{
for (int i = 1; i < nums.size(); i++)
{
for (int j = 0; j < nums.size()-i; j++)
{
if (nums[j] > nums[j + 1]) swap(nums[j], nums[j + 1]);
}
}
}
int main()
{
vector<int> vec = { 2,1,6,5,3,5,2,7 };
Maopao_sort(vec);
for (auto it :vec)
{
cout << it << endl;
}
}
桶排序
#include<iostream>
#include<vector>
#include<string>
using namespace std;
void Bucket_sort(vector<int>& vec)
{
int max = vec[0];
int s = vec.size();
for (int i = 0; i < s; i++) //最大值
{
if (vec[i] > max)
{
max = vec[i];
}
}
vector<int> res(max + 1); //创建
for (int i = 0; i < vec.size(); i++) //记录
{
res[vec[i]]++;
}
//还原
int Index = 0;
for (int i = 0; i < res.size(); i++)
{
while (res[i] > 0)
{
vec[Index++] = i;
res[i]--;
}
}
}
int main()
{
vector<int> vec = { 2,1,6,5,3,5,2,7 };
Bucket_sort(vec);
for (auto it :vec)
{
cout << it << endl;
}
}
选择排序:
#include<iostream>
#include<vector>
#include<string>
using namespace std;
vector<int> Sort(vector<int>& nums)
{
int s = nums.size();
for (int i = 0; i < s ; i++)
{
int MaxIndex = 0;
int j = 1;
for (; j< s - i; j++)
{
if (nums[MaxIndex] < nums[j])
{
MaxIndex = j;
}
}
swap(nums[MaxIndex], nums[j - 1]);
}
return nums;
}
int main()
{
vector<int> vec = { 1,8,9,5,4,1,3 };
/*vector<int> chars;*/
vector<int> chars = Sort(vec);
for (auto it : chars)
{
cout << it << endl;
}
return 0;
}
堆排序
void Adjust(vector<int> &vec,int start,int end)
{
int father = start;
int child = father * 2 + 1;
while (child + 1 <= end)
{
if(child <= end && vec[child] < vec[child + 1])
{
child++;
}
if (vec[father] < vec[child])
{
swap(vec[father], vec[child]);
father = child;
child = father * 2 + 1;
}
else
{
break;
}
}
}
void Tree::Heap_Sort(vector<int>& vec)
{
int n = vec.size();
for (int i = n / 2 + 1; i >= 0;i--)
{
Adjust(vec, i, n - 1);
}
for (int i = n - 1; i >= 0; i--)
{
swap(vec[0], vec[i]);
Adjust(vec, 0, i - 1);
}
}