C/C++基础知识点

C和C++的区别

  1. C++是C的超集,C是面向过程化的结构性语言,而C++是面向对象的编程语言
  2. C语言更偏向于底层,使用较为灵活,可移植性强,而C++更偏向于上层,可扩展性强,对于大型项目往往使用C++
  3. C++在C语言的基础上提出了STL标准模板库,函数模板等特性

static关键字的作用

  1. 隐藏,凡事变量前添加static关键字,只对该变量所在的文件显示,对其他文件隐藏
  2. 默认初始化为0,对于变量前加static关键字,未经初始化前该变量会被自动初始化为0
  3. 保持变量的持久化
  4. 用来修饰类的成员函数和成员变量,类的成员变量前加static关键字,该成员将属于整个类,而非类的某个对象;类的静态成员函数同理。其无this指针,仅能访问static修饰的成员函数和变量。

static全局静态变量和局部静态变量的区别

  1. 默认初始化为0
  2. 作用域。
    1. static全局静态变量在声明它的文件之外是不可见的,准确来说,从定义开始到文件结尾。
    2. static局部静态变量的作用域当定义在它的函数或者语句块结束时,作用域结束。但是当局部静态变量离开作用域时并没有被销毁,而是仍然驻留在内存中,只不过无法对其进行访问,直到该函数再次被调用,并且值不变。

C++中四种cast类型转换

  1. const_cast用于将const转换为非const类型
  2. static_cast用于所有的隐式转换(类似于C语言的强转类型),同时也用于上行转换(将子类转为父类),父类转子类也可行,但是类型不安全
  3. dynamic_cast即可以用于上行转换也可以用于下行转换,下行转换不成功会返回NULL,有安全检查
  4. reprint_cast用于所有类型和指针之间的转换

C++中指针和引用的区别

  1. 指针不必初始化,引用必须初始化且只能作为同一变量的别名
  2. 指针一般指的是某块内存的地址,通过这个地址,可以访问到这块内存;而引用只是一个变量的别名
  3. 指针可以指向任何类型;而引用只能指向一个变量
  4. 指针可以为NULL;而引用不可以为空

C++中智能指针原理、用法和缺陷

原理

智能指针是一个指针类,利用了析构函数的原理,离开作用域时释放指针对象。引入智能指针的目的是为了防止程序员在创建了指针,使用完成后,未进行释放导致内存泄漏问题。

用法

auto_ptr:

C++98提出的,定义在库中,只能用来管理单个动态创建的对象,而不是管理动态创建的数组

Auto_ptr不足之处:
  1. 两个auto_ptr不能指向同一块内存,析构时会造成同一块内存多次释放,程序崩溃;
  2. 不要将auto_ptr对象作为STL容器的元素,C++标准中禁止这样使用;
  3. 不能将数组作为auto_ptr的参数;

unique_ptr:

与share_ptr不同,unique_ptr没有定义类似make_share的操作,因此只能使用new来分配内存,不可通过拷贝和赋值,初始化时必须使用直接初始化的方式。
例如:

nique_ptr <int> up1(new int()); // ok
unique_ptr <int> up2 = new int(); // error
unique_ptr <int> up3(up2); // error

与share_ptr不同,unique_ptr拥有它所指向的对象,在某一时刻,只能有一个unique_ptr指向特定的对象。当unique_ptr被销毁时,它所指向的对象也会被销毁。因此不允许多个unique_ptr指向同一个对象,所以不允许拷贝与赋值。

如何传递unique_ptr参数和返回unique_ptr呢?

A. 可以拷贝或赋值一个将要被销毁的unique_ptr

// 从函数返回一个unique_ptr
unique_ptr func1(int a){
   Return unique_ptr <int> (new int(a));
}
// 返回一个局部对象的拷贝
unique_ptr func1(int a){
  unique_ptr <int> up(new int(a));
  return up;
}

B. 传unique_ptr参数可以使用引用避免所有权的转移,或者暂时的移交所有权

void func1(unique_ptr <int> &up){
  Cout<<*up<<endl;
}

unique_ptr <int> func2(unique_ptr <int> up){
  cout<<*up<<endl;
  return up;
}
// 使用up作为参数
unique_ptr <int> up(new int(10));

// 传引用,不拷贝,不涉及所有权转移
Func1(up);

// 暂停转移所有权,函数结束时返回拷贝,重新收回所有权
up = func2(unique_ptr <int> (up.release()));

share_ptr:

通过引用计数的方式来实现多个share_ptr对象共享同一块资源。

缺陷:
  1. 不要与裸指针混用;
  2. 不要用p.get()的返回值为share_ptr赋值,因为p.get()返回值是普通指针;
  3. 两个类,每个类包含share_ptr指针互相指向对方,会造成循环引用,导致内存泄漏

weak_ptr:

为了解决循环引用问题。原理是weak_ptr并不影响shared_ptr引用计数指针的计数值,即weak_ptr不会影响指向区域内存的生命周期

数组和指针的区别

概念

数组用于存放多个相同类型的集合,而指针用来存放变量在内存中的地址

赋值

同类型指针可以相互赋值,而数组需要一个一个元素赋值或者拷贝

存储

数组的存储空间不是在静态区就是在栈上,而指针无法确定

求sizeof

数组大小 = sizeof(数组名)/ sizeof(数据类型),而指针 32位:4,64位:8

const的用法

用来修饰指针变量

  1. const位于*的左侧,表示指针所指变量是常量,不可通过解引用来修改其值
  2. const位于*的右侧,表示指针本身是常量,不可修改指针所指向的地址
  3. const位于*的左右两侧,表示都不可改变

用来修饰函数参数

  1. 对于指针类型的参数,需要加const防止指针被意外修改
  2. 对于非内部数据结构的参数,修改为const引用方式,提高效率

用来修饰函数返回值

  1. 用const修饰的函数返回值,不可修改其返回值,且必须用同类型的const变量接收

用来修饰类成员函数体

  1. 修饰类的成员函数,表明该成员变量不可修改,否则会报错

new/delete和malloc/free的区别

  1. new/delete是C++的关键字;而malloc/free是C语言中的函数
  2. new/delete在创建的时候调用C++的构造/析构函数,而malloc/free不需要
  3. new创建对象时不需要指定大小,而malloc需要指定内存分配大小
  4. new创建成功后返回创建的对象,是类型安全的,无需转换;而malloc需要强转指定类型

C++内存是如何分配

  1. 从栈上分配。函数中的临时局部变量分配在栈上,由操作系统自动分配,函数调用结束时内存也随之析构,栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
  2. 从堆上分配。在堆区使用malloc或new申请内存,这种内存分配方式非常灵活。
  3. 从静态存储区上分配。这块内存在程序编译的时候就已经分配好,用来存放常量,全局变量和static变量,内存在整个程序运行周期内都存在。
  4. 全局/静态存储区。
  5. 常量存储区。

为什么需要内存对齐及如何关闭内存对齐?

内存对齐:

为了提高程序的性能,数据结构(尤其是栈)应该尽可能在自然边界上对齐。原因是,为了访问未对齐的内存,处理器需要两次内存访问;然而,对齐的内存仅需访问一次。

如何关闭内存对齐?

一种是添加#pragma pack(1); 另一种是利用__attribute__(packed)指令。用法如下

#pragma pack(1)
struct A {
  int a;
  int b;
  char c;
};
sizeof(A); //输出9

struct B{
  int a;
  int b;
  char c;
}__attribute__((packed))B;
sizeof(B); //输出9

sizeof和strlen的区别

  1. strlen是函数,而sizeof是运算符;
  2. strlen测量的是字符的实际长度,以“\0”结束,但不包括“\0”所占大小;
  3. sizeof是用来计算字节或类型大小,一般由机器决定,而非人为控制
  4. strlen是在运行时计算长度的,而sizeof是在编译时确定长度大小的;

堆和栈的区别

  1. 申请方式:栈区内存由系统自由分配,函数结束后自动释放,而堆区则由程序员自行分配,用完后自行释放;
  2. 申请空间大小:栈默认是1M,可进行修改,最大是8M,而堆则需要看主机是32位还是64位;
  3. 申请效率:栈快于堆;
  4. 存取效率:栈快于堆;
  5. 底层不同:栈是连续的空间,而堆是不连续的空间
  6. 是否产生碎片化:对于堆来讲,频繁的new/delete势必造成内存空间的不连续,因此堆容易产生碎片化,而栈不会;
  7. 生长式向:堆是向上生长的,也就是向着内存地址增加的方向,而栈恰恰相反。

#define的用法以及与typedef的区别

  1. define是C语言定义的语法,是预处理指令,在预处理时只做简单的字符串替换不做安全检查,只有在编译被展开的源程序时才会发生可能的错误并报错;
  2. typedef是关键字,在编译处理时,有类型安全检查。是为一个已存在的类型起别名。

在C中用const能定义真正意义上的常量吗?C++中的const呢?

不能。C中的const仅仅从编译层来限定,不允许对const变量进行赋值操作,在运行期是无效的,所以并非真正的常量。但是C++中是有区别的,c++在编译时会把const常量加入符号表,以后(仍然在编译期)遇到这个变量会从符号表中查找,所以在C++中是不可能修改到const变量的。

补充:

  1. c中的局部const常量存储在栈空间,全局const常量存在只读存储区,所以全局const常量也是无法修改的,它是一个只读变量。 2. 这里需要说明的是,常量并非仅仅是不可修改,而是相对于变量,它的值在编译期已经决定,而不是在运行时决定。
  2. c++中的const 和宏定义是有区别的,宏是在预编译期直接进行文本替换,而const发生在编译期,是可以进行类型检查和作用域检查的。
  3. c语言中只有enum可以实现真正的常量。
  4. c++中只有用字面量初始化的const常量会被加入符号表,而变量初始化的const常量依然只是只读变量。
  5. c++中const成员为只读变量,可以通过指针修改const成员的值,另外const成员变量只能在初始化列表中进行初始化。

malloc是否是线程安全的,如何实现线程安全?

malloc函数是一个我们经常使用的函数,如果使用不对会造成一些潜在的问题。下面就malloc函数的线程安全性和可重入性做一些分析。

我们知道一个函数要做到线程安全,需要解决多个线程调用函数时访问共享资源的冲突。而一个函数要做到可重入,需要不在函数内部使用静态或全局数据,不返回静态或全局数据,也不调用不可重入函数。

malloc函数线程安全但是不可重入的,因为malloc函数在用户空间要自己管理各进程共享的内存链表,由于有共享资源访问,本身会造成线程不安全。

为了做到线程安全,需要加锁进行保护。同时这个锁必须是递归锁,因为如果当程序调用malloc函数时收到信号,在信号处理函数里再调用malloc函数,如果使用一般的锁就会造成死锁(信号处理函数中断了原程序的执行),所以要使用递归锁。

虽然使用递归锁能够保证malloc函数的线程安全性,但是不能保证它的可重入性。按上面的场景,程序调用malloc函数时收到信号,在信号处理函数里再调用malloc函数就可能破坏共享的内存链表等资源,因而是不可重入的。

至于malloc函数访问内核的共享数据结构可以正常的加锁保护,因为一个进程程调用malloc函数进入内核时,必须等到返回用户空间前夕才能执行信号处理函数,这时内核数据结构已经访问完成,内核锁已释放,所以不会有问题。

栈溢出,内存溢出

内存溢出内存溢出 :

对于一台服务器而言,每一个用户请求,都会产生一个线程来处理这个请求,每一个线程对应着一个栈,栈会分配内存,此时如果请求过多,这时候内存不够了,就会发生栈内存溢出。

栈溢出栈溢出 :

栈溢出是指不断的调用方法,不断的压栈,最终超出了栈允许的栈深度,就会发生栈溢出,比如递归操作没有终止,死循环。

posted @ 2023-08-10 14:59  suntl  阅读(34)  评论(0编辑  收藏  举报