struct与class的区别

内部成员变量及成员函数的默认权限属性

struct默认权限属性是public的,而class默认的权限属性是private的

struct A
{
    int iNum;
}
class B
{
    int iNum;
}

A a;
a.iNum = 2;        //没有问题,默认权限属性为public
B b;
b.iNum = 2;        //编译出错,默认权限属性为private

继承关系中默认权限属性的区别

在继承关系,struct默认是public的,而class是private,例如:

struct A
{
    int   iAnum;
}
struct B : A
{
    int   iBnum;
}

A a;
a.iAnum = 1;    //在struct情况下是正确的,在class情况下是错误的

在是struct的情况下B是默认public继承A的。如果将上面的struct改成class,那么B是private继承A的。

上面的列子都是struct继承struct,class继承class,那么class与struct继承会怎样呢?
结论是:默认的权限属性取决于子类而不是基类,例如:

struct A{};
class B : A {};    //默认为private继承
struct C : B{};    //默认为public继承

所以我们在写代码的时候,为了不引起歧义,最好指明继承的方式,而不要用默认的继承,例如:

class B : public A{};
struct B : public A{};

模板中的使用

class这个关键字还可用于定义模板参数,就像typename。但是strcut不用与定义模板参数,例如:

template< typename T, typename Y >    //可以把typename 换成 class
int  Func( const T& t, const Y& y )
{
    //TODO
}

2.4、{}赋初值的讨论

因为C++是对C的扩充,那么它就兼容过去C中struct的特性,例如:

struct A
{
    char     c1;
    int        i2;
    double    db3;
};

A a = {'p', 7, 451.154}; //定义时赋初值,在struct时没问题,在class时出错

当然这里在class时,默认的权限属性为private,所以出错正常,但这是它们之间的一个区别吗?不是的。
在struct中加入一个构造函数(或虚函数),你会发现struct也不能用{}赋值了。嗯?头顶上有个大大的问号。

原因是以{}的方式来赋初值,只是用一个初始化列表来对数据进行按顺序的初始化,如果上面写成A a = {‘p’,7};则c1,i2被初始化,而db3没有。这样简单的copy操作,只能发生在简单的数据结构上,而不应该放在对象上。加入一个构造函数或是一个虚函数会使strcut更体现出一种对象的特性,而是{}操作不在有效。

因为加入这样的函数(构造和虚函数),使得类的内部结构发生了变化。而加入一个普通的成员函数呢?你会发现{}依旧可用。其实你可以将普通的函数理解成对数据结构的一种算法,这并不打破它数据结构的特性。

核心区别就只有一条,默认的读取权限不同。struct是public,而class是private

是否可用于声明模板(struct不可以, class可以)

单纯声明数据集合体就用struct,有属性和操作就用class

C++中static关键字作用总结

总结:

  • (1)静态成员函数中不能调用非静态成员
  • (2)非静态成员函数中可以调用静态成员。因为静态成员属于类本身,在类的对象产生之前就已经存在了,所以在非静态成员函数中是可以调用静态成员的。
  • (3)静态成员变量使用前必须先初始化(如 int MyClass::m_nNumber = 0;),否则会在 linker 时出错。

一般总结:在类中,static 可以用来修饰静态数据成员和静态成员方法。

静态数据成员

  • (1)静态数据成员可以实现多个对象之间的数据共享,它是类的所有对象的共享成员,它在内存中只占一份空间,如果改变它的值,则各对象中这个数据成员的值都被改变。
  • (2)静态数据成员是在程序开始运行时被分配空间,到程序结束之后才释放,只要类中指定了静态数据成员,即使不定义对象,也会为静态数据成员分配空间。
  • (3)静态数据成员可以被初始化,但是只能在类体外进行初始化,若未对静态数据成员赋初值,则编译器会自动为其初始化为 0。
  • (4)静态数据成员既可以通过对象名引用,也可以通过类名引用。

静态成员函数

  • (1)静态成员函数和静态数据成员一样,他们都属于类的静态成员,而不是对象成员。
  • (2)非静态成员函数有 this 指针,而静态成员函数没有 this 指针。
  • (3)静态成员函数主要用来方位静态数据成员而不能访问非静态成员。

指针和引用的区别

  1. 指针保存的是指向对象的地址,引用相当于变量的别名
  2. 引用在定义的时候必须初始化,指针没有这个要求
  3. 指针可以改变地址,引用必须从一而终
  4. 不存在空引用,但是存在空指针NULL,相对而言引用更加安全
  5. 引用的创建不会调用类的拷贝构造函数

new/delete与malloc/free的区别

  1. new是运算符,malloc是C语言库函数
  2. new可以重载,malloc不能重载
  3. new的变量是数据类型,malloc的是字节大小
  4. new可以调用构造函数,delete可以调用析构函数,malloc/free不能
  5. new返回的是指定对象的指针,而malloc返回的是void*,因此malloc的返回值一般都需要进行类型转化
  6. malloc分配的内存不够的时候可以使用realloc扩容,new没有这样的操作
  7. new内存分配失败抛出bad_mallocmalloc内存分配失败返回NULL值

volatile是干啥的

  1. 访问寄存器要比访问内存要块,因此CPU会优先访问该数据在寄存器中的存储结果,但是内存中的数据可能已经发生了改变,而寄存器中还保留着原来的结果。为了避免这种情况的发生将该变量声明为volatile,告诉CPU每次都从内存去读取数据。
  2. 一个参数可以即是const又是volatile的吗?可以,一个例子是只读状态寄存器,是volatile是因为它可能被意想不到的被改变,是const告诉程序不应该试图去修改他。

const的作用

  1. const修饰全局变量;
  2. const修饰局部变量;
  3. const修饰指针,const int *;
  4. const修饰指针指向的对象, int * const;
  5. const修饰引用做形参;
  6. const修饰成员变量,必须在构造函数列表中初始化;
  7. const修饰成员函数,说明该函数不应该修改非静态成员,但是这并不是十分可靠的,指针所指的非成员对象值可能会被改变
  8. const常量与#define宏定义常量的区别:

    1. const常量具有类型,编译器可以进行安全检查;
    2. #define宏定义没有数据类型,只是简单的字符串替换,不能进行安全检查。
  9. 防止修改,起保护作用,增加程序健壮性
  10. 可以节省空间,避免不必要的内存分配:

    const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。
    
  11. 未被const修饰的变量不需要extern显式声明!而const常量需要显式声明extern,并且需要做初始化!因为常量在定义后就不能被修改,所以定义时必须初始化。
  12. 定义时必须初始化

extern关键字作用

声明一个外部变量。

define/const/inline的区别

本质:define只是字符串替换,const参与编译运行,具体的:

  • define不会做类型检查,const拥有类型,会执行相应的类型检查

     . define仅仅是宏替换,不占用内存,而const会占用内存
  • const内存效率更高,编译器通常将const变量保存在符号表中,而不会分配存储空间,这使得它成为一个编译期间的常量,没有存储和读取的操作

本质:define只是字符串替换,inline由编译器控制,具体的:

  1. 内联函数在编译时展开,而宏是由预处理器对宏进行展开
  2. 内联函数会检查参数类型宏定义不检查函数参数 ,所以内联函数更安全
  3. 宏不是函数,而inline函数是函数
  4. 宏在定义时要小心处理宏参数,(一般情况是把参数用括弧括起来)。

构造函数为什么不能定义为虚函数,析构函数为什么可以?

虚函数的执行依赖于虚函数表。而虚函数表需要在构造函数中进行初始化工作,即初始化vptr,让他指向正确的虚函数表。而在构造对象期间,虚函数表还没有被初始化,将无法进行。

在类的继承中,如果有基类指针指向派生类,那么用基类指针delete时,如果不定义成虚函数,派生类中派生的那部分无法析构。

构造函数不要调用虚函数。在基类构造的时候,虚函数是非虚,不会走到派生类中,既是采用的静态绑定。显然的是:当我们构造一个子类的对象时,先调用基类的构造函数,构造子类中基类部分,子类还没有构造,还没有初始化,如果在基类的构造中调用虚函数,如果可以的话就是调用一个还没有被初始化的对象,那是很危险的,所以C++中是不可以在构造父类对象部分的时候调用子类的虚函数实现。但是不是说你不可以那么写程序,你这么写,编译器也不会报错。只是你如果这么写的话编译器不会给你调用子类的实现,而是还是调用基类的实现。

为什么只有在构造函数可以抛出异常,析构函数中不可以

  1.不建议在构造函数中抛出异常;

​ 2.构造函数抛出异常时,析构函数将不会被执行,需要手动的去释放内存

析构函数不能抛出异常,处理析构函数异常的正确方式是将异常封装在析构函数内部,而不是抛出异常,如下代码所示。

~ClassName(){
    try{
        do_something();
    }
    catch{
        //...
    }
}

原因如下:C++异常处理模型有责任处理那些因为出现异常而失效的对象,处理方式是调用这些失效对象的析构函数,释放掉它们占用的资源。如果析构函数再抛出异常,则会陷入无尽递归嵌套之中,因此这是不被允许的。

C++5大存储区域

  1. :是分配给函数局部变量的存储单元,函数结束后,该变量的存储单元自动释放,效率高,分配的空间有限。
  2. 由new创建,由delete释放的动态内存单元。如果用户不释放该内存,程序结束时,系统会自动回收
  3. 自由存储区:由new创建,由delete释放的动态内存单元,与堆类似。
  4. 全局(静态)存储区:全局变量和静态变量占一块内存空间。
  5. 常量存储区:存储常量,内容不允许更改。

堆和栈的区别

实例程序

//main.cpp    
int a = 0;   //全局初始化区    
char *p1;   //全局未初始化区    
int main()
{
    int b;   //栈
    char s[] = "abc"; //栈
    char *p2;  //栈
    char *p3 = "123456";   //123456 / 0在常量区,p3在栈上。
    static int c = 0;   //全局(静态)初始化区
    p1 = (char*)malloc(10);
    p2 = (char*)malloc(20);
    //分配得来得10和20字节的区域就在堆区。
    strcpy(p1, "123456");   //123456 / 0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}
  • 申请方式

    >   **栈**:由系统**自动分配**。例如,声明在函数中一个局部变量   int   b;   系统自动在栈中为b开辟空间
    
    >   **堆**:需要**程序员自己申请,并指明大小**,在c中malloc函数
    >   如p1   =   (char   *)malloc(10);
    >   在C++中用new运算符
    >   如p2   =   new   char[10];
    >   但是注意p1、p2本身是在栈中的。
    
  • 申请后系统的响应

    >   **栈**:**只要栈的剩余空间大于所申请空间,系统将为程序提供内存**,否则将报异常提示栈溢出。
    
    >   **堆**:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
    
  • 申请大小的限制

    >   栈:**在Windows下,栈是向低地址扩展的数据结构**,是一块连续的内存的区域。这句话的意思是**栈顶的地址和栈的最大容量是系统预先规定好的**,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
    
    >   堆:**堆是向高地址扩展的数据结构,是不连续的内存区域**。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
    
  • 申请效率的比较:

    >   **栈:由系统自动分配,速度较快。但程序员是无法控制的。**
    
    >   **堆:是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便**.另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。
    
  • 堆和栈中的存储内容

    >   **栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量**。注意静态变量是不入栈的。
    >   当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
    
    >   **堆:一般是在堆的头部用一个字节存放堆的大小**。堆中的具体内容由程序员安排。
    
  • 存取效率的比较

    >   堆:由C/C++函数库提供,机制很复杂。所以**堆的效率比栈低很多**。
    
    >   栈:是极其系统提供的数据结构,计算机在底层对栈提供支持,分配专门  寄存  器存放栈地址,栈操作有专门指令。
    

什么是红黑树

  • 节点为红色或者黑色;
  • 根节点为黑色;
  • 从根节点到每个叶子节点经过的黑色节点个数的和相同;
  • 如果父节点为红色,那么其子节点就不能为红色。

红黑树与AVL树的区别

  • 红黑树与AVL树都是平衡树,但是AVL是完全平衡的(平衡就是值树中任意节点的左子树和右子树高度差不超过1);
  • 红黑树效率更高,因为AVL为了保证其完全平衡,插入和删除的时候在最坏的情况下要旋转logN次,而红黑树插入和删除的旋转次数要比AVL少。
最后修改:2020 年 07 月 13 日 07 : 31 PM
如果觉得我的文章对你有用,请随意赞赏