C/C++基础知识点
C和C++的区别
- C++是C的超集,C是面向过程化的结构性语言,而C++是面向对象的编程语言
- C语言更偏向于底层,使用较为灵活,可移植性强,而C++更偏向于上层,可扩展性强,对于大型项目往往使用C++
- C++在C语言的基础上提出了STL标准模板库,函数模板等特性
static关键字的作用
- 隐藏,凡事变量前添加static关键字,只对该变量所在的文件显示,对其他文件隐藏
- 默认初始化为0,对于变量前加static关键字,未经初始化前该变量会被自动初始化为0
- 保持变量的持久化
- 用来修饰类的成员函数和成员变量,类的成员变量前加static关键字,该成员将属于整个类,而非类的某个对象;类的静态成员函数同理。其无this指针,仅能访问static修饰的成员函数和变量。
static全局静态变量和局部静态变量的区别
- 默认初始化为0
- 作用域。
- static全局静态变量在声明它的文件之外是不可见的,准确来说,从定义开始到文件结尾。
- static局部静态变量的作用域当定义在它的函数或者语句块结束时,作用域结束。但是当局部静态变量离开作用域时并没有被销毁,而是仍然驻留在内存中,只不过无法对其进行访问,直到该函数再次被调用,并且值不变。
C++中四种cast类型转换
- const_cast用于将const转换为非const类型
- static_cast用于所有的隐式转换(类似于C语言的强转类型),同时也用于上行转换(将子类转为父类),父类转子类也可行,但是类型不安全
- dynamic_cast即可以用于上行转换也可以用于下行转换,下行转换不成功会返回NULL,有安全检查
- reprint_cast用于所有类型和指针之间的转换
C++中指针和引用的区别
- 指针不必初始化,引用必须初始化且只能作为同一变量的别名
- 指针一般指的是某块内存的地址,通过这个地址,可以访问到这块内存;而引用只是一个变量的别名
- 指针可以指向任何类型;而引用只能指向一个变量
- 指针可以为NULL;而引用不可以为空
C++中智能指针原理、用法和缺陷
原理
智能指针是一个指针类,利用了析构函数的原理,离开作用域时释放指针对象。引入智能指针的目的是为了防止程序员在创建了指针,使用完成后,未进行释放导致内存泄漏问题。
用法
auto_ptr:
C++98提出的,定义在
Auto_ptr不足之处:
- 两个auto_ptr不能指向同一块内存,析构时会造成同一块内存多次释放,程序崩溃;
- 不要将auto_ptr对象作为STL容器的元素,C++标准中禁止这样使用;
- 不能将数组作为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对象共享同一块资源。
缺陷:
- 不要与裸指针混用;
- 不要用p.get()的返回值为share_ptr赋值,因为p.get()返回值是普通指针;
- 两个类,每个类包含share_ptr指针互相指向对方,会造成循环引用,导致内存泄漏
weak_ptr:
为了解决循环引用问题。原理是weak_ptr并不影响shared_ptr引用计数指针的计数值,即weak_ptr不会影响指向区域内存的生命周期
数组和指针的区别
概念
数组用于存放多个相同类型的集合,而指针用来存放变量在内存中的地址
赋值
同类型指针可以相互赋值,而数组需要一个一个元素赋值或者拷贝
存储
数组的存储空间不是在静态区就是在栈上,而指针无法确定
求sizeof
数组大小 = sizeof(数组名)/ sizeof(数据类型),而指针 32位:4,64位:8
const的用法
用来修饰指针变量
- const位于*的左侧,表示指针所指变量是常量,不可通过解引用来修改其值
- const位于*的右侧,表示指针本身是常量,不可修改指针所指向的地址
- const位于*的左右两侧,表示都不可改变
用来修饰函数参数
- 对于指针类型的参数,需要加const防止指针被意外修改
- 对于非内部数据结构的参数,修改为const引用方式,提高效率
用来修饰函数返回值
- 用const修饰的函数返回值,不可修改其返回值,且必须用同类型的const变量接收
用来修饰类成员函数体
- 修饰类的成员函数,表明该成员变量不可修改,否则会报错
new/delete和malloc/free的区别
- new/delete是C++的关键字;而malloc/free是C语言中的函数
- new/delete在创建的时候调用C++的构造/析构函数,而malloc/free不需要
- new创建对象时不需要指定大小,而malloc需要指定内存分配大小
- new创建成功后返回创建的对象,是类型安全的,无需转换;而malloc需要强转指定类型
C++内存是如何分配
- 从栈上分配。函数中的临时局部变量分配在栈上,由操作系统自动分配,函数调用结束时内存也随之析构,栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
- 从堆上分配。在堆区使用malloc或new申请内存,这种内存分配方式非常灵活。
- 从静态存储区上分配。这块内存在程序编译的时候就已经分配好,用来存放常量,全局变量和static变量,内存在整个程序运行周期内都存在。
- 全局/静态存储区。
- 常量存储区。
为什么需要内存对齐及如何关闭内存对齐?
内存对齐:
为了提高程序的性能,数据结构(尤其是栈)应该尽可能在自然边界上对齐。原因是,为了访问未对齐的内存,处理器需要两次内存访问;然而,对齐的内存仅需访问一次。
如何关闭内存对齐?
一种是添加#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的区别
- strlen是函数,而sizeof是运算符;
- strlen测量的是字符的实际长度,以“\0”结束,但不包括“\0”所占大小;
- sizeof是用来计算字节或类型大小,一般由机器决定,而非人为控制
- strlen是在运行时计算长度的,而sizeof是在编译时确定长度大小的;
堆和栈的区别
- 申请方式:栈区内存由系统自由分配,函数结束后自动释放,而堆区则由程序员自行分配,用完后自行释放;
- 申请空间大小:栈默认是1M,可进行修改,最大是8M,而堆则需要看主机是32位还是64位;
- 申请效率:栈快于堆;
- 存取效率:栈快于堆;
- 底层不同:栈是连续的空间,而堆是不连续的空间
- 是否产生碎片化:对于堆来讲,频繁的new/delete势必造成内存空间的不连续,因此堆容易产生碎片化,而栈不会;
- 生长式向:堆是向上生长的,也就是向着内存地址增加的方向,而栈恰恰相反。
#define的用法以及与typedef的区别
- define是C语言定义的语法,是预处理指令,在预处理时只做简单的字符串替换不做安全检查,只有在编译被展开的源程序时才会发生可能的错误并报错;
- typedef是关键字,在编译处理时,有类型安全检查。是为一个已存在的类型起别名。
在C中用const能定义真正意义上的常量吗?C++中的const呢?
不能。C中的const仅仅从编译层来限定,不允许对const变量进行赋值操作,在运行期是无效的,所以并非真正的常量。但是C++中是有区别的,c++在编译时会把const常量加入符号表,以后(仍然在编译期)遇到这个变量会从符号表中查找,所以在C++中是不可能修改到const变量的。
补充:
- c中的局部const常量存储在栈空间,全局const常量存在只读存储区,所以全局const常量也是无法修改的,它是一个只读变量。 2. 这里需要说明的是,常量并非仅仅是不可修改,而是相对于变量,它的值在编译期已经决定,而不是在运行时决定。
- c++中的const 和宏定义是有区别的,宏是在预编译期直接进行文本替换,而const发生在编译期,是可以进行类型检查和作用域检查的。
- c语言中只有enum可以实现真正的常量。
- c++中只有用字面量初始化的const常量会被加入符号表,而变量初始化的const常量依然只是只读变量。
- c++中const成员为只读变量,可以通过指针修改const成员的值,另外const成员变量只能在初始化列表中进行初始化。
malloc是否是线程安全的,如何实现线程安全?
malloc函数是一个我们经常使用的函数,如果使用不对会造成一些潜在的问题。下面就malloc函数的线程安全性和可重入性做一些分析。
我们知道一个函数要做到线程安全,需要解决多个线程调用函数时访问共享资源的冲突。而一个函数要做到可重入,需要不在函数内部使用静态或全局数据,不返回静态或全局数据,也不调用不可重入函数。
malloc函数线程安全但是不可重入的,因为malloc函数在用户空间要自己管理各进程共享的内存链表,由于有共享资源访问,本身会造成线程不安全。
为了做到线程安全,需要加锁进行保护。同时这个锁必须是递归锁,因为如果当程序调用malloc函数时收到信号,在信号处理函数里再调用malloc函数,如果使用一般的锁就会造成死锁(信号处理函数中断了原程序的执行),所以要使用递归锁。
虽然使用递归锁能够保证malloc函数的线程安全性,但是不能保证它的可重入性。按上面的场景,程序调用malloc函数时收到信号,在信号处理函数里再调用malloc函数就可能破坏共享的内存链表等资源,因而是不可重入的。
至于malloc函数访问内核的共享数据结构可以正常的加锁保护,因为一个进程程调用malloc函数进入内核时,必须等到返回用户空间前夕才能执行信号处理函数,这时内核数据结构已经访问完成,内核锁已释放,所以不会有问题。
栈溢出,内存溢出
内存溢出内存溢出 :
对于一台服务器而言,每一个用户请求,都会产生一个线程来处理这个请求,每一个线程对应着一个栈,栈会分配内存,此时如果请求过多,这时候内存不够了,就会发生栈内存溢出。
栈溢出栈溢出 :
栈溢出是指不断的调用方法,不断的压栈,最终超出了栈允许的栈深度,就会发生栈溢出,比如递归操作没有终止,死循环。
本文来自博客园,作者:suntl,转载请注明原文链接:https://www.cnblogs.com/stlong/p/17619645.html