剑指offer复盘(二)——C++语言基础

按照剑指offer顺序,第二章内容是关于基础知识的部分。以下主要是关于C++语言的内容总结,主要是针对之前有些不太了解的地方,作一下简略的记录,可能主观性较强,想到哪里就写到哪里,后续会不断补充。与STL和刷题有关的知识会放在另外的地方。

 

关于const

const与指针

const int * p和int * const p的功能呢可以通过结合顺序来理解,前者是常量指针(指向常量的指针变量),指向const int类型,可以修改指针指向的变量但是不能进行(*p=1)操作,后者是指针常量(一个常量),指向int类型,可以进行(*p=1)操作不能进行(p+=1)操作。

 

const与引用

首先看一下引用,引用是一个变量的别名,不占用内存,引用必须被初始化,并且初始化后不可以再修改为指向其他变量。

 

注意初始化的时候并不是用被引用变量的地址,而是直接使用被引用变量。

int a = 1;
int &r = &a; //错误
int &r = a; // 正确

 

const引用可以使用不同的类型进行引用初始化,其相当于是绑定并认为该被变量/表达式是const的。需要注意的是,即使是中间需要进行类型转换(如double到const int &),或者绑定的是临时变量,const引用也是有效的。

 

那么这样的操作有什么用呢?显然,普通的引用的功能是通过这个别名来访问/修改所被引用变量的值:

int a = 1;
int &r = a;
r = 2;

 

下面具体到使用场景上,常见的const引用使用场景是作为函数参数进行传递,比如:

int func(const int & a);

此时,我们不能通过引用a修改对应变量的值。

 

在函数中,为了减少创建pass-by-value创建变量的消耗,所以使用pass-by-reference,但是在pass-by-reference时,又不希望子函数去修改传入变量,这种情况就可以使用const引用来达到目的。

 

const与类成员

const修饰类成员函数时,可以防止这个类成员函数修改类中的成员变量;

  1. int A::fun(int x) const{
        XXX;
    }

同时,只有const类成员函数才能访问const类成员变量。

 

为什么要这样设计呢?个人认为主要是出于封装的考虑,比如一个类Student可能会含有private成员变量unsigned int age,那么如果外界想要访问学生Student的年龄,可以定义一个成员函数unsigned int Student::get_age() const;,通过这样一个const成员函数去获取成员变量的值,避免了暴露内部成员变量。

 

关于static

static关键字的使用主要有以下几种情况:

static静态局部变量

 静态局部变量是在函数内部创建的静态变量,特点是:

  • 数据位于全局存储区
  • 首次执行函数的时候被初始化,然后值一直保持,下一次调用函数的时候不再初始化;
  • 会进行默认初始化
  • 作用域依然是局部的,仅在函数内可以用

static静态全局变量

全局变量定义在函数外,使用static主要是为了实现文件隔离,static的变量只能在本文件内被使用(区别于全局变量)。

static静态函数

与静态全局变量相似,也可以文件隔离。

static静态类成员变量

静态成员是属于类本身,而不是每次实例化一个类就产生新的变量。

static静态类成员函数

思想和上面是一样的,静态成员函数不能访问非静态的成员函数和非静态数据成员,因为它是属于类本身的。

 

关于强制类型转换

首先要理解什么是类型转换,类型转换是指从一种数据类型转换到另外一种数据类型(废话),类型转换有显式explicit和隐式implicit之分,比如一个函数接受参数为double类型,但是传入为int,这个时候会进行隐式类型转换(int到double):

double fun(double a){
  XXX;
  return b;  
}

int x = 10;
double y = fun(x);

而显式类型转换是通过括号()操作,直接把一个变量的类型转化为另一个类型,比如:

double a = 3.14;
int b = (int) a;

以下主要讨论显式类型转换,C语言中对于显式类型转换比较放得开,只要把目标类型扔进括号()就可以(此处不绝对,处理指针要麻烦一些,暂不涉及)。但是在C++中,除了兼容C的这一做法外,更好的选择是使用4个与类型转换有关的关键字进行转换:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast

个人认为,按照以下方法记忆比较好:

static_cast主要用于C++中内置的基本数据类型之间的转换,也可以用于派生类到基类指针/引用之间的转换,但是有以下两种限制:

  • 不用于基类到派生类之间的转换
  • 不能用于添加/删除const限定

为了解决限制1,可以使用dynamic_cast,dynamic_cast可以完成基类到派生类之间指针/引用的转换,其可以进行动态类型检查,因此称为dynamic。

为了解决限制2,可以使用const_cast,const_cast主要用于从指针/引用类型中加入/去除const或者volatile限定。

此外,还剩下reinterrpret_cast,其相对比较万能,可以转化任何内置类型为其他类型,也可以把任何类型指针转化为其他的类型,因为正如名字一样它进行了重解释reinterpret。这个操作并不安全,因此它也不常用。

 

关于OOP概念

OOP的核心概念是继承、封装、多态。以下均为个人理解:

继承

为了提高开发效率,进行代码复用,可以将对象类进行组织,使得对象类的公共性质/功能被提炼出来作为一个类,后面的每个对象对应的类共同享有这一性质。

封装

隐藏具体实现方法,提供接口,为开发创造方便。

多态

有了继承和封装之后,我们想要实现特异性,即不同类对同一功能有不同的做法。这就是多态。

C++多态主要通过虚函数实现,在基类中定义一个函数为虚函数,后面的派生类可以根据自己的需要进行不同的实现。

在实现方法上,拥有虚函数的类拥有虚函数指针。虚函数按照表的方式进行组织,构成虚函数表。在子类中,如果函数覆盖了对应的虚函数,其在虚函数表的对应位置也发生了对应替换,这样完成了多态的实现。

 

C++内存划分

堆、栈、静态存储区、常量区、代码区

  

全局变量使用

可以用引用头文件的方式,也可以用extern关键字。用引用头文件方式,如果变量出错,则在编译期间会报错;如果用extern方式,假定犯了同样的错误,那么在编译期间不会报错,而在连接期间报错。

 

类中访问限制

private是私有类型,只有本类中的成员函数、友元访问;protect是保护型的,本类和继承类可以访问;public是公有类型,任何类都可以访问。

 

智能指针

智能指针存在于<memory>头文件中,主要有shared_ptr、unique_ptr、weak_ptr。

  • shared_ptr中多个智能指针可以共享同一个对象
  • unique_ptr中指针独占对象
  • weak_ptr共享但不拥有某对象,用于打破环状引用

 

内存对齐

需要注意:

  • 每一个成员的偏移量都必须是该成员的倍数
  • 结构体的大小必须是该结构体字节数最大成员的倍数

 

TodoList

  • 构造/析构函数的处理

  • 运算符/函数重载

 

参考资料

https://blog.csdn.net/weixin_38403778/article/details/82290139

https://blog.csdn.net/weixin_43329614/article/details/89103574

https://www.cnblogs.com/junlinfeizixiao/p/6193412.html

https://www.cnblogs.com/chio/archive/2007/07/18/822389.html

https://www.cnblogs.com/suomeimei/p/10513522.html

https://blog.csdn.net/weixin_40311211/article/details/82851300

https://blog.nowcoder.net/n/f73dc0635ae14a7e8643a9dba9b5783c

https://blog.csdn.net/u010189457/article/details/81118855

https://github.com/huihut/interview#cc

posted @ 2020-07-07 21:21  haydenhs  阅读(165)  评论(0)    收藏  举报