C++初级

extern和externC

extern C按照C语言的方式来编译C++的代码。 extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一只般之包括函数名。

malloc/free和new/delete的十点区别

  • malloc是从堆上开辟空间,而new是从自由存储区开辟(自由存储区是 C++抽象出来的概念,不仅可以是堆,还可以是静态存储区);
  • malloc/free是函数,而new/delete是关键字;
  • malloc对开辟的空间大小需要严格指定,而new只需要对象名
  • malloc开辟的空间即可以给单个对象用也可以给数组用,释放的方式都是free();而new开辟对象数组用的是new[size] , 释放的的时候是 delete[]
  • 返回值问题,malloc开辟成功返回void*,需要强转,失败返回NULL,new成功返回对象指针,失败抛出异常(这就可能会提到C++的new_handler机制),虽然为了最大程度的兼容C,C++的new也支持失败返回NULL,但是一般不被使用,大家可以了解一下;
  • 是否调用构造和析构,这点应该放在前面,new和free不但负责开辟空间,还会调用对象的构造函数和析构函数;最好了解一下new的三种表达形式(new运算符,operator new(); placement new();)
  • 是否可以相互调用,new的实现可以用malloc,malloc的实现不可以使用new;
  • 是否可以被重载,我们可以重载自己的operator new、operator delete,但是不可以重载new/delete/malloc/free;
  • malloc开辟的内存如果太小,想要换一块大一点的,可以调用relloc实现,但是new没有直观的方法来改变;
  • 第十点其实前面已经提到,当new中的底层实现如果获取不到更多的内存,会触发new_handler机制,留有一个set_new_handler句柄,看看用户是否设置了这个句柄,如果设置了就去执行,句柄的目的是看看能不能尝试着从操作系统释放点内存,找点内存,如果实在不行就抛出bad_alloc异常;而malloc就没有这种尝试了

C++中struct和class的区别是什么?

解答:C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外C++中struct还可以用来定义类。
和class是定义类是一样的,区别是struct的成员默认访问方式是public,class是struct的成员默认访问方式
是private。

引用和指针的不同点:

  1. 引用在定义时必须初始化,指针没有要求
  2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型
    实体
  3. 没有NULL引用,但有NULL指针
  4. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占
    4个字节)
  5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
  6. 有多级指针,但是没有多级引用
  7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  8. 引用比指针使用起来相对更安全

怎样打印一个char*的地址

只要我们cout后面的输出对象是一个char* 的类型时,它都会当作要输出这个地址指向的字符串来执行。它会从这个地址开始输出字符,直到遇到’\0’停止。那我们怎样才能让cout输出char * 类型的地址呢?
c是靠%s,%x,%p来区分指针表达式;c++没有这个格式控制,只能按一种形式输出,c++标准库中I/O类对输出操作符<<重载,在遇到字符型指针时会将其当做字符串名来处理,输出指针所指的字符串。 既然是这样,我们只需要将char* 类型的指针进行强制转换成别的类型的指针,cout就会输出指针存储的地址。我们可以把它强转成void* 类型,然后就可以输出地址了

为何空类的大小是1?

其实在早期C++的编译器中,空类的大小是0,但是这样做的话就导致了当创建对象的时候他们和后面紧随的对象具有相同的地址,因此所以在现在大多数编译器中,该值的大小为1

不同位置对象的构造顺序

  • 全局区定义的对象的构造顺序是不确定的,不同的编译器使用不同的构造规则
  • 局部对象:当程序执行流到达对象构造语句时进行构造
  • 堆对象:当程序执行流执行到new关键字时创建对象,new创建对象时会自动调用构造函数

构建一个类的对象的一般顺序

  1. 如果该类有直接或者间接的虚基类,则先执行虚基类的构造函数
  2. 如果该类有其他基类,则按照它们在继承声明的列表中出现的次序,分别执行它们的构造函数。
  3. 按照在类定义中出现的顺序,对派生类中新增的成员对象进行初始化。对于类类型的成员对象,如果出现在构造函数初始化列表中,则以其中指定的参数执行构造函数。如果未出现,则执行默认构造函数;对于基本数据类型的成员对象,如果出现在构造函数的初始化列表中,则使用其中指定的值为其赋初值,否则什么也不做。
  4. 执行构造函数的函数体。

转换构造函数

单参数构造函数
C++中,一个参数的构造函数(或者除了第一个参数外其余参数都有缺省值的多参构造函数),承担了两个角色:用于构建单参数的类对象以及隐含的类型转换操作符

类型转换函数
1、类型转换函数只能定义一个类的成员函数而不能定义为类的友元函数,类型转换类型也可以在类体中声明函数原型,而将函数体定义在类的外部
2、类型转换函数既没有参数,也不能在函数名前指定函数返回类型
3、类型函数中必须有return语句,必须送回目标类型的数据作为函数的返回值

explicit关键字的作用

首先需要了解什么是隐式转换,即在你没有进行显示的强转的情况下,赋值运算符左右两个类型不一致的对象进行了类型转换;或者函数传参的时候进行了类型转换; 而explicit关键字存在的目的就是禁止类的构造函数进行隐式的类型转换,常见的就是string类的对象就可以隐式类型转换。c++中的explicit关键字抑制由构造函数定义的隐式转换。explicit关键字只能用于类内部的构造函数声明上。在类的定义体外部所做的定义上不要加explicit关键字。通常,除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为 explicit。将构造函数设置为explicit可以避免错误,并且当转换有用时,用户可以显式地构造对象。explicit对于拷贝构造函数同样也有限制作用,将会阻止隐式拷贝构造函数的调用。

为什么需要命名空间

目的是对标识符的名称进行本地化,以避免命名冲突或名字污染

命名空间的使用

1) 加命名空间名称以及作用域限定符
2) 使用using 将命名空间中的成员引入
3) 使用using namespace将整个命名空间引入。

缺省参数注意

1) 半缺省参数必须从右往左依次来给出,不能间隔着给
2) 缺省参数不能在函数声明和定义中同时出现
3) 缺省值必须是常量或者全局变量

名字修饰

        名字修饰是一种在编译过程中,将函数、变量的名字重新改编的机制,简单来说就是编译器为了区分各个函数,将函数通过某种算法,重新修饰为一个全局唯一的名称。C语言中的名字修饰非常简单,只是在函数名字前面添加了下划线,而在C++中则相对复杂一些,需靠考虑到命名空间、类作用域、参数列表。

名字修饰的结果

作用域+函数名+参数列表

什么是函数重载

        函数重载是指在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数。重载函数通常用来命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。

为什么需要函数重载

1)试想如果没有函数重载机制,如在C中,你必须要这样去做:为这个print函数取不同的名字,如print_int、print_string。这里还只是两个的情况,如果是很多个的话,就需要为实现同一个功能的函数取很多个名字,如加入打印long型、char*、各种类型的数组等等。这样做很不友好!
2)类的构造函数跟类名相同,也就是说:构造函数都同名。如果没有函数重载机制,要想实例化不同的对象,那是相当的麻烦!
3)操作符重载,本质上就是函数重载,它大大丰富了已有操作符的含义,方便使用,如+可用于连接字符串等!

为什么不将函数返回类型考虑到函数重载中呢

这是为了保持解析操作符或函数调用时,独立于上下文(不依赖于上下文)

重载函数的调用匹配

  • 精确匹配:参数匹配而不做转换,或者只是做微不足道的转换,如数组名到指针、函数名到指向函数的指针、T到const T;
  • 提升匹配:即整数提升(如bool 到 int、char到int、short 到int),float到double
  • 使用标准转换匹配:如int 到double、double到int、double到long double、Derived到Base、T到void、int到unsigned int;
  • 使用用户自定义匹配;
  • 使用省略号匹配:类似printf中省略号参数

引用

        引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会给引用变量开辟内存空间,他和她引用的变量公用同一块内存空间。
        不能将栈上的空间作为引用类型返回,如果以引用类型返回,返回值的生命周期必须不受函数的限制

范围for循环的使用条件

        1) for循环迭代的范围必须是确定的
        2) 迭代的对象要实现++和==的操作

nullptr和nullptr_t

在C++98中,字面常量0既可以是一个整形数字,也可以是一个无类型的指针常量,但是编译器默认情况下将其看成是一个整形常量,如果要按照指针方式来使用,必须对其进行强转(void*)0。
        1) 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为关键字引入的
        2) 在C++11中,sizeof(nullptr)与sizeof((void*) 0)所占字节数相同
        3) 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr

        声明和定义全部放在类体中,需要注意的是,成员函数如果在类中定义,编译器可能会将其当做内联函数处理,一般建议写一个cpp就写一个.h文件
        访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。访问限定符只在编译时有用,当数据映射到内存之后,没有任何访问限定符上的区别。

C++中struct和class的区别是什么

        C++需要兼容C语言,所以C++中struct可以当成结构体去使用,另外C++中struct还可以用来定义类。和class定义类是一样的,区别是struct的成员默认访问方式是public,class中成员的默认访问方式是private。继承方式同上

结构体内存对齐规则

        1) 第一个成员在与结构体偏移量为0的地址处
        2) 其他成员要对齐到某个数字(对齐数)的整数倍的地址处
                a) 对齐数=编译器默认的一个对齐数与该成员大小的较小值
                b) vs中默认的对齐数是8,gcc中的对齐数是4
        3) 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍
        4) 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处(结构体的对齐数为结构体当中所有对齐数中的最大对齐数)

为什么要进行内存对齐?

struct A {
char c;
int i;
}
        若无内存对齐情况下,按照连续存储时,1234 5678作为8字节,在结构体中,char c会存储在1号位上,而int i会存储在2345位上,而CPU在读取在访问c的时候,每次访问4个字节,没有什么问题,会先拿出1—4,再拿出5—8,但是int i被切割开了,仍需要做字节切割及字节拼接,效率很低。如果进行内存对齐时,将char c存放在1号位,再偏移3个字节,将int i存储在5–8号位,这样CPU进行访问的时候,不必做字节上的拼接和切割,效率会大大提高。是一种典型的空间换时间以提高效率的方式。

如何让结构体按照指定的对齐参数进行对齐?

        #pragma pack(4) 用来指定默认对齐数

如何知道结构体中某个成员相对于结构体起始位置的偏移量?

        &(((A1 *)0)->f)

this指针

        C++编译器给每个成员函数增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问,只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
        1)this指针的类型:类类型*const
        2)只能在非静态成员函数的内部使用
        3)this指针本质上其实是一个成员函数的形参并且是第一个形参,通过ecx寄存器自动传递,因此this指针并不在对象中。

this指针在哪儿

        在非静态的成员函数中

类的默认成员函数

        1) 构造函数
        2) 析构函数
        3) 拷贝构造
        4) 赋值重载
        5) 取地址函数(很少自己实现)
        6) const取地址函数(很少自己实现)

构造函数

        构造函数的任务并不是开空间创建对象,而是初始化对象
特征
        1) 函数名与类型相同
        2) 无返回值
        3) 对象实例化时编译器自动调用对应的构造函数
        4) 构造函数可以重载
        5) 在对象的声明周期中只能调用一次
        6) 通过无参构造函数创建对象时,对象后面不可以加括号
        7) 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器不再生成
        8) 无参的构造函数和全缺省的构造函数以及编译器默认生成的构造函数统称为默认构造函数,默认构造函数只能有一个
        9) 构造函数会自动调用其成员的构造函数(自定义类型成员)

析构函数

析构函数不是完成对象的销毁,而是完成类的一些资源清理工作
        1) 析构函数名是在类名前加上一个~
        2) 无参数无返回值
        3) 一个类有且只有一个析构函数,若未显式定义,系统会自动生成默认的析构函数
        4) 对象生命周期结束时,C++编译系统自动调用析构函数
        5) 编译器生成的默认析构函数,会对自定义类型成员调用它的析构函数

拷贝构造函数

        只有单个参数,该参数是对本类类型对象的引用(一般用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用
        1) 拷贝构造函数是构造函数的一个重载形式
        2) 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式将会引发无穷递归调用
        3) 若未显式定义,系统生成默认的拷贝构造函数,默认的拷贝构造函数对于内置类型将会按照字节序完成拷贝,对于类类型变量将会调用其构造函数

运算符的重载

        1) 不能通过连接其他符号来创建新的操作符:比如operator@
        2) 重载操作符必须有一个类类型或者枚举类型的操作数
        3) 用于内置类型的操作符,其含义不能改变,例如+
        4) 作为类成员的重载函数时,其形参看起来比操作数数目少1。因为成员函数有一个默认的参数this,限定为第一个形参
        5) .*、::、sizeof、?:、.这五个运算符不能重载
        6) 不改变优先级、结合性以及操作数个数

赋值运算符

        1) 参数类型
        2) 返回值
        3) 检测是否自己给自己赋值
        4) 返回*this
        5) 一个类如果没有显式定义赋值运算符重载,编译器会自动生成的赋值运算符对于内置类型将会按照字节序完成拷贝,对于类类型变量将会调用其赋值运算符函数

初始化列表

        构造函数体中的语句只能将其称作为赋初值,而不能称为初始化,因为初始化只能初始化一次,而构造函数体内可以多次赋值。如果想要完成初始化,我们可以使用初始化列表。
注意:
        1) 每个成员变量在初始化列表中只能出现一次
        2) 类中包含以下成员,必须放在初始化列表位置进行初始化
                a) 引用成员变量
                b) const成员变量
                c) 类类型成员变量(该类没有默认构造函数)
                d) 当继承自一个不具有默认构造的基类
        3) 尽量使用初始化列表完成初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化
        4) 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
        5) 编译器会对初始化列表一一处理并可能重新排序以反映出成员声明的顺序,他会安插一些代码到构造函数体重,并放在任何显式用户代码之前

构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用。
用explicit修饰构造函数,将会禁止单参构造函数的隐式转换。

静态成员

分类:静态成员变量,静态成员变量一定要在类外进行初始化
静态成员函数
特性:
        1) 静态成员 为所有类对象所共享,不属于某个具体的实例
        2) 静态成员变量必须在类外初始化,初始化时不添加static关键字
        3) 类静态成员即可用类名::静态成员或者对象.静态成员来访问
        4) 静态成员函数没有隐含的this指针,不能访问任何非静态成员
        5) 静态成员和普通成员一样,也有public,protected,private3种访问级别,可以有返回值,const修饰符(静态成员函数除外)等
        6) 基类定义了static静态成员,则整个继承体系中只有一个这样的成员,无论派生出多少个子类,都只有一个static成员实例。

static 关键字

  • 修饰全局变量时,会将变量的链接属性变为内部链接属性,并且变量的存储位置变为全局静态区;
  • 修饰局部变量,改变局部变量的存储位置为静态存储区,改变局部变量的生命周期为整个程序开始到结束;
  • 修饰类的成员变量和函数:属于类而不属于对象,属于所有实例类;
    C++11中支持非静态成员变量在声明时,直接初始化

友元

分类:友元函数和友元类
        友元提供了一种突破封装的方式,有时提供了便利,但是友元会增加耦合度,破坏封装,所以友元不宜多用
友元函数
        友元函数可以直接访问类的私有成员,他是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字,友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员
说明
        1) 友元函数可访问类的私有成员,但不是类的成员函数
        2) 友元函数不能用const修饰,因为他就不是成员函数
        3) 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
        4) 一个函数可以是多个类的友元函数
        5) 友元函数的调用与普通函数的调用和原理相同
友元类
        友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员
注意:
        1) 友元关系是单向的,不具有交换性。
        2) 友元关系不能传递

内部类

        内部类,如果一个类定义在另一个类的内部,这个内部类就叫做内部类,注意,内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类,外部类对内部类没有任何优越的访问权限。内部类就是外部类的友元类,但是外部类不是内部类的友元。
特征:
        1) 内部类可以定义在外部类的public,protected,private都是可以的
        2) 注意内部类可以直接访问外部类中的static,枚举成员,不需要外部类的对象或类名
        3) sizeof(外部类)= 外部类,与内部类无关。
        4)内部类可以通过外部类的对象参数来访问外部类中的所有成员。
        5) 总起来一句话,就是内部类和外部类没什么关系,只是内部类是外部类的友元类

进程地址空间

正文段:这是CPU执行的机器指令部分、只读常量,通常,正文段是可共享的,所以即使是频繁执行的程序在存储器中也只需要一个副本,另外正文段常常是只读的,以防止程序由于意外而修改其指令。
初始化数据段:通常将此段称为数据段,它包含了程序中明确赋初值的变量
未初始化数据段(bss段):在程序开始执行之前,内核将此段中的数据初始化为0或者空指针。
栈:又叫做堆栈,非静态局部变量、函数参数、返回值等等,栈是向下增长的
堆:用于程序运行时动态存储分配,堆是向上增长的。
内存映射段:是高效的IO映射方式 ,用于装载一个共享的动态内存库,用户可以使用系统提供的接口创建一个共享内存来实现进程间通信。
在这里插入图片描述

malloc、calloc以及realloc的区别

在这里

new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。

什么是内存泄漏

        内存泄漏是指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况,内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该内存的控制,因而造成了内存的浪费。
内存泄漏包括:堆内存泄漏和系统资源泄漏

什么是泛型编程

        泛型编程:编写与类型无关的通用代码,是代码复用的一种手段,模板是泛型编程的基础

函数模板

        函数模板代表了一个函数家族,该函数模板与类型无关,在使用的时候被参数化,根据实参类型产生函数的特定类型版本
        模板是一个蓝图,它本身并不是函数,是编译器根据不同的使用方式产生特定具体类型的函数的模具,所以模板就是将本来应该我们做的重复的事情交给了编译器
        在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。

函数模板的实例化

        用不同类型的参数使用函数模板时,称为函数模板的实例化,模板参数实例化分为:隐式实例化和显式实例化
        隐式实例化:让编译器根据实参推演模板参数的实际类型。隐式实例化中,编译器一般不会进行类型转换操作,因为一旦转换出了问题,编译器就要背黑锅。
        显式实例化:在函数名后<>中指定模板参数的实际类型。

模板匹配规则

        1) 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
        2) 对于非模板函数和同名的函数模板,如果其他条件都相同,在调用时会优先调用非模板函数而不会从该模板产生一个实例,如果模板可以产生一个具有更好匹配的函数,那么将选择模板。
        3) 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

模板类

        模板类不是类,是编译器根据被实例化的类型生成具体类的模具。
        类模板的实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真实的类,而实例化的结果才是真实的类。

对于输入输出缓冲区的理解

        1) 可以屏蔽掉低级IO的实现,低级IO的实现依赖操作系统本身内核的实现,如果能够屏蔽这部分的差异,可以很容易的写出可移植的程序
        2) 可以使用这部分的内容实现行读取的行为,因为计算机中是没有行这个概念的,有了IO缓冲区就可以定义行的概念,然后解析缓冲区的内容,返回一个行。

流的注意点

        1) cin为缓冲流,键盘输入的数据保存在缓冲区中,当要提取时,是从缓冲区中拿,如果输入过多会留在那儿慢慢用,如果输入错了,必须在回车之前修改,如果按下回车键就无法挽留了,只有把输入缓冲区中的数据取完后,才要求输入新的数据,不可能用刷新来清空缓冲区,所以不能输错,也不能多输
        2) 输入的数据类型必须与要提取的数据类型一致,否则出错,出错只会将流的状态字state中对应位置置一,程序继续。
        3) 空格和回车都可以作为数据之间的分隔符,所以多个数据可以在一行输入,也可以分行输入,但是如果是字符型或者字符串,则空格无法用cin输入,字符串也不能有空格,回车符无法读入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值