@[C++笔记]
零、基础中的基础
1、 LINUX中C++环境配置:
sudo apt-get install gcc g++
sudo apt-get install lsb-core lib32stdc++6 // 安装其他库
测试是否安装成功:
g++ -v
gcc -v
2、 INT_MAX / INT_MIN
C/C++整型上下限INT_MAX INT_MIN
因为int占4字节32位,根据二进制编码的规则,INT_MAX = 2^31-1,INT_MIN= -2^31.C/C++中,所有超过该限值的数,都会出现溢出,出现warning,但是并不会出现error。如果想表示的整数超过了该限值,可以使用长整型long long 占8字节64位。
C++中INT_MAX的使用
//INT_MAX是个很大的数,如果想得到数组中最小值,可以先将minn设成INX_MAX(作为初始比较值),这样就不需要const int定义一个很大的数了,还有很多地方都可以使用它
int minn=INT_MAX;
for(int i=0;i<n;i++){
if(arr[i]<minn)minn=arr[i];
}
一、C++基础
C++面向对象的三大特征:封装、继承和多态;
0、C++基本数据结构
1. 栈
- 栈的常见分类:
(1)基于数组的栈——以数组为底层数据结构时,通常以数组头为栈底,数组头到数组尾为栈顶的生长方向
(2)基于单链表的栈——以链表为底层的数据结构时,以链表头为栈顶(头插法),便于节点的插入与删除,压栈产生的新节点将一直出现在链表的头部
- C++实现
使用标准库的栈时, 应包含相关头文件,在栈中应包含头文件: #include< stack > 。
定义:stack< int > s;
s.empty(); //如果栈为空则返回true, 否则返回false;
s.size(); //返回栈中元素的个数
s.top(); //返回栈顶元素, 但不删除该元素
s.pop(); //弹出栈顶元素, 但不返回其值
s.push(); //将元素压入栈顶
- 基于数组的栈
#include <stack>
#include <iostream>
using namespace std;
int main()
{
stack<int> mystack;
int sum = 0;
for (int i = 0; i <= 10; i++){
mystack.push(i);
}
cout << "size is " << mystack.size() << endl;
while (!mystack.empty()){
cout << " " << mystack.top();
mystack.pop();
}
cout << endl;
system("pause");
return 0;
}
//size is 11
// 10 9 8 7 6 5 4 3 2 1 0
- 基于单链表的栈
#include <iostream>
using namespace std;
template<class T>class Stack //使用模板
{
private:
struct Node
{
T data;
Node *next;
};
Node *head;
Node *p;
int length;
public:
Stack()
{
head = NULL;
length = 0;
}
void push(T n)//入栈
{
Node *q = new Node;
q->data = n;
if (head == NULL)
{
q->next = head;
head = q;
p = q;
}
else
{
q->next = p;
p = q;
}
length++;
}
T pop()//出栈并且将出栈的元素返回
{
if (length <= 0)
{
abort();
}
Node *q;
T data;
q = p;
data = p->data;
p = p->next;
delete(q);
length--;
return data;
}
int size()//返回元素个数
{
return length;
}
T top()//返回栈顶元素
{
return p->data;
}
bool isEmpty()//判断栈是不是空的
{
if (length == 0)
{
return true;
}
else
{
return false;
}
}
void clear()//清空栈中的所有元素
{
while (length > 0)
{
pop();
}
}
};
int main()
{
Stack<char> s; // 此处是不是相当于把“T”定义成了“char”?
s.push('a');
s.push('b');
s.push('c');
while (!s.isEmpty())
{
cout << s.pop() << endl;
}
system("pause");
return 0;
}
2. 队列
queue是一种容器转换器模板,调用
#include< queue>
即可使用队列类。
- queue初始化
queue<Type, Container> //(<数据类型,容器类型>)
初始化时必须要有数据类型,容器可省略,省略时则默认为deque 类型
//默认为用deque容器实现的queue;
queue<int>q1;
queue<double>q2;
queue<char>q3;
//用list容器实现的queue
queue<char, lis<char>>q1;
//用deque容器实现的queue
queue<int, deque<int>>q2;
注意:不能用vector容器初始化queue
因为queue转换器要求容器支持front()、back()、push_back()及 pop_front(),说明queue的数据从容器后端入栈而从前端出栈。所以可以使用deque和list对queue初始化,而vector因其缺少pop_front(),不能用于queue。
- 常用函数
push() 在队尾插入一个元素
pop() 删除队列第一个元素
size() 返回队列中元素个数
empty() 如果队列空则返回true
front() 返回队列中的第一个元素
back() 返回队列中最后一个元素
- 用例
//push()在队尾插入一个元素
queue <string> q;
q.push("first");
q.push("second");
cout<<q.front()<<endl;
1、C++与C不同之处
1.程序模板的差别
C++
#include <iostream> //C++标准输入输出头文件
#include <string>
using namespace std; //std是一个命名空间
class Dog{ //定义类
public:
string name; //成员变量
void run(){ } //成员函数
};
int main(){ //主函数
Dog dog1; //*从栈中实例化对象并对成员变量初始化(方式1)
Dog *dog2 = new Dog(); //*从堆中实例化对象 (方式二)
return 0;
}
C
#include <stdio.h>
int main(void){
return 0;
}
2.
2、 [ STL / 容器 / 模板 ]
1.STL初识
STL(Standard Template Library),即标准模板库
软件界一直希望建立一个可重复利用的东西,为了建立数据结构和算法的一套标准,STL孕育而生;
STL是C++标准库的一部分。主要组成部分是容器,迭代器,算法。
1.STL包含
1.容器(container)
2.算法(algorithmn)
3.迭代器(iterator)
容器和算法之间通过迭代器进行无缝连接
2.STL六大件
1.容器
2.算法
3.迭代器
4.仿函数
5.适配器(配接器)
6.空间配置器
算法通过迭代器访问容器中的数据
2.容器(vector Stack deque)
参考CSDN
容器定义:(容器是一种对象类型)
在数据存储上,有一种对象类型,它可以持有其它对象或指向其它对像的指针,这种对象类型就叫做容器。
容器是一个模板类,它用于存放各种类型的数据,如基本类型的变量或者对象等,C++中的容器就是存储对象的一种介质,容器最最重要的优点就是可以自己扩展自己的大小
容器分为三大类:关联式容器,顺序性容器和容器适配器。根据存储形式进行划分的。
STL中定义容器模板类的头文件有8个
- 容器使用(以vector为例)
在使用时先包含对应的头文件,然后在对应的变量前加上你的容器类型,例如
#include<vector>
int main()
{
vector<int> v(3,2);//3个元素,初值为2
//两种赋值方式
v[0]=100; //第一个元素赋值为100
v.at(1) = 200;//第二个元素赋值为200
for(int i=0;i<3;i++) //输出所有元素值
cout<<v.at(i)<<' ';
cout<<endl;
system("pause");
return 0;
}
1.关联容器(map set…)
上面给出的这些关联容器都是非线性的树结构,内部都是用红黑树这种二叉树结构实现的。
-
set
字面意思集合,其作用也就是一个集合,里面不允许有相同的元素,其内部是通过链表的方式来组织的,每个元素只包含一个关键字。set是保证有序的,每次插入元素之后都会自动按升序排列。 -
multiset
顾名思义,可重集合,内部实现方式和set相同,它允许内部的元素重复,同一个元素可以出现多次 -
unordered_set
无序集合,是一个存储唯一元素的关联容器,容器中的元素无特别的秩序关系,该容器允许基于值的快速元素检索,同时也支持正向迭代。其内部实现与s e t setset不同,是用哈希表实现的。 -
unordered_multiset
和上面一样的,元素可以重复的无序集合。
4. 顺序容器(deque vector list)
-
deque
双端队列,可以在队列的首尾进行操作,其内部实现更加复杂,支持对所有元素的随机访问,它的随机访问和遍历性能比vector差。它不像vector一样有连续的存储空间,而是多个连续的内存块。应用举例就是spfa的slf优化。 -
vector
vector是一种线性顺序结构,它的大小和所用内存是不定的,会在使用中自动分配。vector是一个类模板,像vector才叫一种数据类型。它的存储空间是连续的。 -
list
这个基本是用的最少的了,它是一个线性双向链表结构。它的随机访问是要按顺序查找,并不像vector一样直接找到地址。内存空间和deque一样不是连续的。
5.容器适配器(stack queue priority_queue)
-
stack
支持先进后出,是一个线性表,插入和删除只在表的一端进行,它不提供元素的任何迭代器操作。由于它的的内部使用的是其他容器,因此它可看做是一种适配器,作用就是将一种容器转换为另一种容器。它的模版需要定义两个模版参数,一个是元素类型,一个是容器类型,元素类型是必要的,容器类型是可选的,默认为deque类型。 -
queue
queue与上面的stack十分相似,支持先进先出 -
priority_queue
在queue的头文件中还定义了一个十分有用的模板类,它的特点就是队头的元素一定是优先级最高的元素,和进队顺序无关,这使它的应用范围特别广
3、初识容器:
1. vector
- vector与普通数组相似,但可以动态扩展,可以往里面插元素;
- 连续存储结构,每个元素在内存上是连续的;
- 支持高效的随机访问和在尾端插入/删除操作,但其他位置的插入/删除操作效率低下;
2. stack
3. deque
- 连续存储结构,即其每个元素在内存上也是连续的,类似于vector,不同之处在于,deque提供了两级数组结构,第一级完全类似于vector,代表实际容器;另一级维护容器的首位地址。
- 这样,deque除了具有vector的所有功能外,还支持高效的首端插入/删除操作。
4. list
-
非连续存储结构,具有双链表结构,每个元素维护一对前向和后向指针,因此支持前向/后向遍历。
-
支持高效的随机插入/删除操作,但随机访问效率低下,且由于需要额外维护指针,开销也比较大。
5.
4、模板
针对C++泛型编程和STL技术
模板(类型参数化“T”)
建立一个通用型模具,大大提高复用性,类型参数化泛型编程,主要利用的技术1就是模板;
/*模板案例*/
//声明一个模板,告诉编译器后面代码中紧跟着的“T”不要报错,“T”是一个通用数据类型;
template <typename T>
void mySwap(T &a, T &b){
T temp = a;
a = b;
b = temp:
}
//使用模板(函数)
/*法1:*/
int a = 10;
int b = 20;
mySwap(a,b);
/*法2:*/
mySwap<int>(a, b); //推荐用法2 ???
1.函数模板
2、类、封装、继承和多态
1、类和对象
1. 类基础知识
类是C++的核心特征,类用于指定对象的形式:
例如Dog为狗这一大类、dog1可以作为一个对象;
成员变量:name age;
1.类内权限
累在设计时,可以把属性和行为放在不同的权限下,加以控制,访问权限有三种:
1.public 公共权限 类内可访问 类外可访问
2.protected 保护权限 类内可访问 类外不可访问
3.private 私有权限 类内可访问 类外不可访问
2.从类中实例化对象两种方式:
从栈中实例化对象 从堆中实例化对象
/*class.cpp*/
#include <iostream>
#include <string>
using namespace std;
class Dog{ //定义类
public:
string name; //成员变量
int age;
void run(){
cout<<"name: "<<name<<"\n"<<"age: "<<age<<endl; //输出流
}
};
int main(){ //主函数
Dog dog1; //*从栈中实例化对象
dog1.name="xiaohuang";
dog1.age=2;
dog1.run();
Dog *dog2 = new Dog(); //*从堆中实例化对象 (指针对象都得这么实例化吗?)
if(NULL ==dog2){ //如果申请的内存空间为空,则直接返回
return 0;
}
dog2.name="xiaohei";
dog2.age=3;
dog2.run();
return 0;
}
随后可以在LINUX中编译然后执行:
g++ class.cpp -o CLASS 编译
./CLASS 执行
3.构造函数与析构函数
实际上定义类时,如果没有定义构造函数和析构函数,
编译器就 会生成一个构造函数和析构函数。
构造函数:在对象实例化时被系统自动调用,仅且调用一次。
析构函数:在对象结束其生命周期时系统自动执行析构函数。
4. this 指针-----------------------
this指针一般在成员函数里用:
void Dog::func() //类外定义函数用::(作用域限定符)
{
this->name = "小黄";
cout<<"name:"<<this->name<<endl;
}
谁(一般是对象)调用这个函数,this就 指向谁
this指针的特点:———————————————————
( 1 )每个当前对象都含有一个指向该对象的this指针。this指针只能在类的成员函数中使用,在全局函数、静态成员函数中都不能使用 this 。
( 2 ) this 指针是在成员函数的开始前构造,并在成员函数的结束后清除 。
( 3 ) this 指针会因编译器不同而有不同的存储位置,可能是寄存器或全局变量 。
( 4 ) this 是类的指针 。
( 5 ) 因为 this 指针只有在成员函数中才有定义,所以获得一个对象后,不能通过对象使 用 this 指针,所以也就无法知道一个对象的 this 指针的位置。 不过,可以在成员函数中指定this 指针的位置 。
( 6 )普通的类函数(不论是非静态成员函数,还是静态成员函数)都不会创建一个函数表来保存函数指针,只有虚函数才会被放到函数表中。
5. 构造函数 / ~析构函数
参考CSDN
- 构造函数和析构函数的特点
- 构造函数和析构函数是类的一种特殊的公有成员函数,每一个类都有一个默认的构造函数和析构函数;
- 构造函数在类定义时由系统自动调用,析构函数在类被销毁时由系统自动调用;
- 构造函数的名称和类名相同,一个类可以有多个构造函数,只能有一个析构函数。不同的构造函数之间通过参数个数和参数类型来区分;
- 我们可以在构造函数中给类分配资源,在类的析构函数中释放对应的资源。
- 不带参数的构造函数
C++类在创建时,系统会默认创建一个不带参数的构造函数,我们可以重新定义这个构造函数。默认构造函数在类被创建时自动调用,,定义的类在main函数返回后被销毁,此时系统会自动调用析构函数,回收分配给类的资源。
class Person
{
public:
int age;
Person();
~Person();
private:
int weight;
protected:
int hight;
};
Person::Person()//重定义默认构造函数
{
cout << "constructor!" << endl;
}
Person::~Person()//重定义默认析构函数
{
cout << "destructor!" << endl;
}
int main()
{
class Person person;//定义类后系统自动调用构造函数
return 0;//函数返回,类被销毁,系统自动调用析构函数
}
//执行结果
3. 使用构造函数初始化类的成员
- 类的构造函数可以直接访问类内部的所有成员,可以通过构造函数初始化类的成员。
- 类成员的初始化方式有两种,第一种是直接在构造函数中给变量赋值,第二种是使用类似于类成员引用的方式初始化。
方式一:
Person::Person(int ag,int we,int hi)
{
age = ag;
weight = we;
hight = hi;
cout << "age:!" << age <<" weight:"<<weight<<" hight:"<<hight<< endl;
}
方式二:
Person::Person(int ag,int we,int hi):age(ag),weight(we),hight(hi)
{
cout << "age:!" << age <<" weight:"<<weight<<" hight:"<<hight<< endl;
}
2、继承
面向对象程序设计中最重要的一个概念;
1. class 派生类 :access-specifier 基类
class derived-class: access-specifier base-class
访问修饰符 access-specifier 是 public、protected 或 private 其中的一个,base-class 是之前定义过的某个类的名称。如果未使用访问修饰符 access-specifier,则默认为 private。
1. 公有继承(public):当一个类派生继承公有基类时,基类的公有成员也是派生类的公有成
员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但
是可以通过调用基类的公有和保护成员来访问。
2. 保护继承(protected): 当一个类派生继承保护基类时,基类的公有和保护成员将成为派
生类的保护成员。
3. 私有继承(private):当一个类派生继承私有基类时,基类的公有和保护成员将成为派生类 的私有成员。
class Dog : public Animal
2. 重载: 函数重载和运算符重载
重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不相同。
3、多态
多态:C++多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数;
形成多态必须具备三个条件:
- 必须存在继承关系;
- 继承关系必须有同名虚函数(其中虚函数是在基类中使用关键字 virtual 声明的函数,在派
生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数); - 存在基类类型的指针或者引用,通过该指针或引用调用虚函数。
1. 虚函数:
是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数;
想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接或后期绑定。
虚函数声明如下:
virtual ReturnType FunctionName(Parameter)
虚函数必须实现,如果不实现,编译器将报错;
纯虚函数:virtual void funtion1()=0;
包含纯虚函数的类是抽象类,抽象类不能定义实例,但是可以声明指针对象;
/* 声明一个 Animal 的指针对象,注:并没有实例化 */
37 Animal *animal;
/* 实例化 dog 对象 */
39 Dog dog;
43 /* 存储 dog 对象的地址 */
44 animal = &dog;
45 /* 调用 run()方法 */
46 animal->run();
虚函数是 C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。
4、数据封装
4.1封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念;
数据封装是一种把数据和操作数据的函数捆绑在一起的机制,
数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。
5、接口(抽象类)
设计抽象类(通常称为 ABC)的目的,是为了给其他类提供一个可以继承的适当的基类。
抽象类不能被用于实例化对象,它只能作为接口使用。
3、
算法题总结
1.链表
- 力扣链表题链表结构体
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {} //疑问1
* };
*/
class Solution {
public:
ListNode* removeDuplicateNodes(ListNode* head) { //疑问2
}
};
疑问1
这行代码为结构体ListNode创建了一个构造函数。该构造函数将结构体成员初始化。与普通的构造函数不同的是,这里使用C++的初始化列表的语法。结构体和类的构造函数类似。下面是初始化列表语法的介绍:
疑问2