1,第一遍:知道。直接看答案,不要自己想,了解所有最优解,方法技巧第一。做题套路,以印象为主。建立一个较为完整的刷题思维体系。
2,第二遍:熟悉。过easy题,记住;做medium,重点题背,反复背。最简单会,大多不会。记住做题套路,以记住为主。
3,第三遍:做题。做easy题;做部分medium题,hard题有思路。夯实medium基础。熟练运用做题套路,以做题为主。
4,面经:做面经,开阔思路,了解出题形式。基础决定上层建筑,基础牢轻松,不牢就痛苦
开发:2个困难,测开:各1个困难 中等
C语言和C++的区别与联系:添加链接描述
添加链接描述
C 可以做应用程序吗1、当然可以,理论上讲C可以完成所有的工作,但是考虑到实际工作量C只能做一部分软件。C主要是用在嵌入式中,一些对性能要求较高的系统软件的底层也用C来做。C++是面向对象的语言,在开发大型的软件中在结构上比C更清晰。两个语言都可以开发软件,只是侧重点不太一样。
2、C作为编程语言,它最主要的用途是在硬件方面,做一些嵌入式的直接对应硬件的控制软件,很少用于开发系统软件。而且系统软件是十分复杂与大型的软件,不可能由哪一种编程语言独立开发,特别是现在的系统越来越复杂与繁琐。
模板
template<class T1,class T2>
template<typename T1, typename T2>
template<class T1,typename T2>.
类:描述对象有那些属性,有那些方法(函数),对象:类的具体的体现。
封装:数据+方法
缺省参数
#include <iostream>
using namespace std;
void TestFunc(int a = 10, int b = 20, int c = 30)
cout << "a = " << a << endl;
cout << "b =”<< b << endl ;
cout<< "c = " << c << endl;
//对于全缺省具有多种调用方式
int main(){
TestFunc(); // 可以全部都缺省,结果= 10 20 30
printf("\n");
TestFunc(1);//缺省2个,结果= 1 20 30
printf("\n") ;
TestFunc(1,2) ://缺省1个,结果= 1 2 30
printf("\n") ;
TestFunc(1,2,3);//都不缺省,结果= 1 2 3
}
必须从左往右缺省
#include <iostream>
using namespace std;
void TestFunc(int a, int b = 20, int c = 30)
cout << "a = " << a << endl;
cout << "b =”<< b << endl ;
cout<< "c = " << c << endl;
//对于半缺省具有多种调用方式
int main(){
TestFunc(1);//缺省2个,对于没有默认值的就必须传,结果= 1 20 30
printf("\n") ;
TestFunc(1,2) ://缺省1个,结果= 1 2 30
printf("\n") ;
TestFunc(1,2,3);//都不缺省,结果= 1 2 3
}
引用
C++中,自定义类型对象传参,尽量不要使用传值传参(拷贝构造),尽量使用引用传参,这样可以减少一次拷贝构造。
对于常量使用的是const修饰,在引用的时候也一定要加const
const int& ra=NULL;//空值NUILL不能引用,这样做没有意义
inline
使用内联函数以后,就不在消耗指针去调用函数,而是直接遇见就展开,当有循环或者递归的时候不适用inline,消耗的空间太大。
class A1 {
public:
void f1(){}
private:
int _a;
};//sizeof(A1) : 4
class A2
{
public:
void f2() {}// sizeof(A2) :1
};
构造函数
类名(形参参数)//构造函数的声明/原型
类名(类名& 对象名)//拷贝构造函数的声明/原型
class Date{
public:
void Init(int year,int month,int day){
_year = year;
_month = month;
_day = day ;
}
void Print (){
cout<<_year <<"-"<<_month << "-"<<_day << endl;
}
private :
int _year;
int _month;
int _day;
};
int main(){
Date d1;
d1.Print();
Date d2;
d2.Init(2021,2,6);
d2.Print() ;
return 0;}
结果:一858993460——858993460——858993460(随机值)
2021-2-6.
无参构造函数和有参构造函数不能同时存在,不然调用会存在歧义,如果写了1个全缺省的构造函数同时还存在一个无参的构造函数, 此时你是调用的全缺省函数还是调用的无参的构造函数呢?更好的写法:直接给一个全缺省的构造函数,既可以使用无参的情况,也可以使用有参的情况
有的类没有拷贝构造的函数,每个类能有多个构造函数。
类的构造函数如果都不是public访问属性,通过静态方法,类的实例可以创建:
class Test{
public:
static Test* GetInst(){//静态方法,应用场景:单例模式
return new Test;
}
private:
Test(){
cout << "Test::Test ()" << endl;
}
};
void main(){
Test *pt = Test:: GetInst();
}//结果:Test::Test ()
赋值、初始化的区别:
赋值操作中调用了重载“=”的函数,初始化中的“=”调用的是默认的构造函数,
构造函数:调用的时候好似通过参数传进来的那个对象来初始化一个对象,赋值函数就是一个已经初始化的对象来进行operator=操作;
拷贝构造是特殊的构造函数,3种应用场景:
1:A a1;
A a2(a1);用一个对象去初始化同一个类的另一个新对象
传参时调用拷贝构造函数,函数的形参对象,调用函数进行形参和实参结合时:
2:void fun(A a)
{}
函数的返回值是类的对象,函数执行返回调用时:
3:A fun()
{a是临时变量,函数结束后,返回的不是a,是a的一份拷贝
A a;return a;
}
以下代码共调用7次拷贝构造函数:
widget f(widget u){F(x)第1次:传参到u;第5次:用w构造临时对象,用于返回值
widget v(u) ;用u拷贝构造v
widget w=v;用已有的对象v拷贝构造新对象w,不是赋值
return w;用w构造临时对象,用于返回值
}
main(){
widget x;
widget y=f(f(x) ) ;
}
当函数返回值是类类型对象时,编译器可能会对返回值类型进行优化:9次改为7次
题目:A为一个类,执行下面语句,会自动调用该类构造函数6次
A a[5],*b[3],c(3),*d,
A *e=new A
A &f=c;//引用
b:指针数组,每个元素都是一个A*类型指针,放地址,指向A类型的3个连续的空间,没有创建对象
a:有5个A对象的一个数组,调用5次A类的构造函数
c:创建对象
e:在堆上创建对象
析构函数
日期类的析构函数是没有用的,没有任何需要清理的资源,系统所默认生成的就可以用
对于栈来说,需要析构函数清理资源:
class Stack{
public:
Stack(int capacity = 4){
_a = (int*)ma11oc(sizeof(int)*capacity);
_size = 0;
__capacity = capacity;
}
~Stack(){
free(_a);
_a = nul1ptr;
_size = _capacity = 0;}
对象里面存的是成员变量,成员函数的地址没有存在对象中;不同文件,多个全局对象,谁先析构是不确定的
题目:A类只能通过new来创建(即如果直接创建对象,编译器将报错),应该:将析构函数(可以定义在类外)设为私有
然后,再给A类增加一个公有的方法:
void Release(A*& p){
delete p;}
否则,没法析构,会内存泄露。
~A{
delete this;不能在析构函数中delete this,形成了无限递归,程序运行时候会栈溢出而导致程序崩溃
}
释放对象中资源,但并不是所有的对象都是从堆上new出来的,so不一定用delete,
this指针的指向不能被修改
const对象只能调用const类型成员函数构造函数和析构函数都可以是虚函数
运算符重载
运算符重载:为了让自定义类型可以像内置类型一样去用运算符,增强程序的可读性。每个运算符具体如何实现,是根据类型的意义来定的,并且一个并不是每个运算符都有重载,有意义才重载,日期+日期=无意义,日期*日期=无意义=不重载,日期+天数=有意义=重载;运算符重载可以重载为全局;一般情况下,运算符重载成全局的函数,不好访问成员变量,不好实现,所以一般情况下,都是实现成成员函数。双目运算符的:有2个参数;
运算符重载2种方式:1.重载成类的成员函数—形参数目看起来比该运算符需要的参数个数少1,成员函数有隐藏的this;2.重载成类的友元函数----必须有一个参数要是类类型的对象。
重载前缀1元运算符为成员函数时,其参数表中没有任何参数,Date& operator++():前缀,后缀:Date operator++(int)。
初始化和赋值的不同含义是拷贝构造函数调用的原因。事实上,拷贝构造函数是由普通构造函数和赋值操作符共同实现的
赋值运算符只能作为类的成员函数重载
const
const void print();//返回值是const
void const print();
void print() const;//const成员函数=常成员函数=常函数,const对象只能调用const类型成员函数
void print(const);//参数是const
const A& a;//a不能被修改,A是类
static
class A{
private :
const static int a = 10;类内初始化
static int b;声明
};
int A:: b=20;类外初始化,定义,用类名调用
初始化列表
#include<iostream>
#include <string>
using namespace std;
class Time
{
public:
Time(int hour = 0, int second = 0)//构造函数
:_hour(hour)
, _second(second)
{
cout << "Time(int hour = 0, int second = 0)" << endl;
}
Time& operator=(const Time& t)//赋值
{
cout << "Time& operator=(const Time& t)" << endl;
return *this;
}
private:
int _hour;
int _second;
};
//方法1:不用初始化列表时:
class Date
{
public:
Date(int year, int month, int day, int hour, int second)
{
_year = year;
_month = month;
_day = day;
Time t(hour, second);//构造对象
_t = t;//对象赋值给_t
}
//方法2:用初始化列表时:
/*public:
Date(int year, int month, int day, int hour, int second)
:_t(hour, second)
{
_year = year;
_month = month;
_day = day;
}*/
private:
int _year; // 声明
int _month;
int _day;
time _t;
};
int main()
{ date d(2020, 1, 1, 1, 1);//d:对象;变量进行私有化,函数进行公有
return 0;}
类中包含以下成员,必须在定义时进行初始化:
自定义类型成员(该类没有默认构造函数)
class time
{
public:
time(int hour = 0)//方法1
:_hour(hour)
{
cout << "~hour()" << endl;//结果:打印出了~hour()
}
private:
int _hour;
};
class Date
{//this指针类型:Date *const this
public:
Date(int year)
{
_year = year;
}
private:
int _year;
time _t;
};
int main()
{
Date d1(2021);
return 0;
}
class A {
public:
A(int a)//方法2:没有默认构造函数(默认成员函数)时
:_a(a)
{}
private:
int _a;
};
class B {
public:
B(int a)
:_aobj(a)
{}
private:
A _aobj; // 没有默认构造函数
};
part1:
class Solution {
public:
int StrToInt(string str) {
int n = str.size();
//……
}}
class string
{
string(const char* str)
{}
private:
// ...
};
int main()
{ string str("1234");
cout << Solution().StrToInt(str) << endl;//方法1
cout << Solution().StrToInt(string("1234")) << endl;//方法2 匿名对象
cout << Solution().StrToInt("1234") << endl;//方法3,那这里其实就是隐式类型,转换+优化-》直接调用函数的结果
return 0;}
new/delete对比malloc/free
1、用法的区别2、是否调用构造函数和析构函数问
C语言处理错误的方式一般是返回错误码,所以malloc失败返回0
C++处理错误的方式一般抛异常,所以operator new和new失败抛昇常
new等价于 operator new +构造函数
operator new等价于 malloc + malloc失败抛异常
delete等价于 析构函数+ operator delete
operator delete 等价于 free
申请空间的是虚拟内存,通过共享映射到达物理内存,
模拟实现底层不是为了用其,而是为了明白到达是什么,
走在城市路的司机只用会开车即可,到藏区、无人区的司机(程序员)需要知道发动机(底层)的基础知识,大致知道哪里出问题,如何修。至少拿2个靠谱的offer,去大公司:不易被裁,去好部门:年终奖;
链表的节点ListNode通过重载类专属 operator new/ operator delete,实现链表节点使用内存池申请和释放内存,提高效率。频繁的申请和释放结点,这个过程会不停的和系统打交道,且产生内存碎片的问题,问题很大且效率也会很低;专属重载以后,系统直接给1块内存的位置,想要申请空间,就到里面去找地方开空间=内存池,系统不要一直管你的事情了,系统来说进程实在太多。这样以后new不再调用全局的operator new,而是取调用你专属重载的operator new。
迭代器
原生指针就是天然的迭代器,迭代器时像指针一样的类型,但是不一定是指针。auto的原理就是根据后面的值,来自己推测前面的类型是什么。auto的作用就是为了简化变量初始化,如果这个变量有一个很长很长的初始化类型,就可以用auto代替。
std::vector<std::string> ve;
std::vector<std::string>::iterator it = ve.begin();
我们可以用atuo来代替那个初始化类型:
auto it = ve.begin();
c_str()
int main()
{string s("hello");
s.push_back('\0');
s.push_back('x');
s.push_back('x');
s.push_back('x');
for (auto e: s)
{
cout << e << " ";
}
cout << endl;//结果:h e l l o X X X
cout << s.c_str() << endl;//hello,见‘\0’就相当于到头
cout << s << endl;//hello XXX,不遇见最后的‘\0’是不会停止的
return 0;
}