主要参考《阿秀笔记》,次要参考博客和侯捷老师视频及C++ Primer
在main函数调用之前和之后执行的代码是什么?(侯捷——C++程序的生前死后)
结构体内存对齐问题
结构体内成员按照声明顺序存储,第一个成员地址和整个结构体地址相同。
未特殊说明时,按结构体中size最大的成员对齐(若有double成员,按8字节对齐。)
struct alignas(4) Info2 {
uint8_t a;
uint16_t b;
uint8_t c;
};
指针和引用的区别
在传递函数参数时,什么时候该使用指针,什么时候该使用引用呢?
堆和栈的区别
new和delete是如何实现的?
new的本质:调用operator new分配内存,将该内存进行转型后赋值给指针,然后调用构造函数进行赋值
Complex *pc = new Complex(1, 2);
// 用c语言进行其编译功能的解释
Complex *pc;
try{
//分配内存,实质调用malloc
void *mem = operator new(sizeof(Complex));
// 内存转型,赋值给指针
pc = static_cast(mem);
// 调用构造函数实例化内存(赋值)
pc->Complex::Complex(1, 2);
}catch(std::bad_alloc){
// 若allocation失败就不执行constructor
}
operator new的本质:使用malloc进行分配内存,同时进行异常处理。可以重载
// 第二参数保证函数不抛出异常
void *operator new(size_t size, const std::nothrow_t &){
void *p;
// 如果内存耗尽导致分配失败 (实质调用malloc)
while((p=malloc(size)) == 0){
_TRY_BEGIN
if(_callnewh(size) == 0)// 调用自定义函数进行处理
break;
_CATCH(std::bad_alloc)
return 0;
_CATCH_END
}
return p;
}
delete的本质:先调用析构函数处理类对象,后调用operator delete函数进行释放。operator delete函数本质是调用free函数
delete pc;
// 使用c语言进行翻译
pc->~Complex();// 先析构对象
operator delete(pc);// 后释放
//operator delete源码
void __cdecl operator delete(void *p)_THROW0(){
free(p);
}
区别以下指针类型
int *p[10]; // 指针数组,数组内的每个元素int类型的指针
int (*p)[10]; // 数组指针,是一个指向具有10个元素的数组的指针变量
int *p(int); // 函数声明,函数名是p,参数是int类型的,返回值是int*类型的
int (*p)(int); // 函数指针,指向参数为int类型并返回值也为int类型的函数
new / delete 与 malloc / free的异同
相同点
不同点
根本不同:new/delete底层使用的是malloc/free,还封装了对象创建时候的构造函数,和销毁的时候要执行的析构函数。被free回收的内存是立即返还给操作系统吗?
不是的,被free回收的内存会首先被ptmalloc使用双链表保存起来,当用户下一次申请内存的时候,会尝试从这些内存中寻找合适的返回。这样就避免了频繁的系统调用,占用过多的系统资源。同时ptmalloc也会尝试对小块内存进行合并,避免过多的内存碎片
malloc和free是标准库函数,支持覆盖。new和delete是运算符,支持重载
空间分配上,new可以自动计算空间分配大小,malloc需要手工计算
安全性上,new是类型安全的,malloc不检查分配内存是否可以被内存直接使用
malloc和free返回的是void类型指针(必须进行类型转换),new和delete返回的是具体类型指针。
new和delete实现不同,见上
被free回收的内存是立即返还给操作系统吗?
不会,被free回收的内存会首先被ptmalloc使用双链表保存起来,当用户下一次申请内存的时候,会尝试从这些内存中寻找合适的返回。避免了频繁的系统调用,提高系统资源利用率。同时ptmalloc也会尝试对小块内存进行合并,避免过多的内存碎片
宏定义和函数有何区别?
宏定义和typedef的区别
变量声明和定义的区别
strlen和sizeof区别?
一个指针占的字节数?
64位处理器上64位操作系统的32位编译器,指针大小8字节。
64位处理器上32位操作系统的16位编译器,指针大小4字节。32位处理器上32位操作系统的32位编译器,指针大小4字节。
32位处理器上32位操作系统的16位编译器,指针大小2字节。32位处理器上16位操作系统的16位编译器,指针大小2字节。
16位处理器上16位操作系统的16位编译器,指针大小2字节。
常量指针和指针常量区别?
指针常量是指向常量的指针,int const *p / const int *p
常量指针:指针是个常量,不能改变指向,必须初始化且之后不能改变
int *const p
int a[10];int (*p)[10];a和&a有什么区别?
int(*)[10],+1表示数组首地址加整个数组的偏移量,即数组末尾下一个元素的地址C++和Python的区别
C++和C语言的区别
C++和Java的区别
C++中struct和class的区别
define宏定义和const的区别
C++中const和static的作用
static关键字:
修饰全局变量时,表明一个全局变量只对定义在同一文件中的函数可见。
修饰局部变量时,在全局数据区分配内存,不会因函数终止而丢失
修饰函数时,表明该函数不能被其他文件所用,而其他文件可以定义同名函数
修饰类的数据成员,表明该实例归所有该类对象共有。
修饰类内的函数,该函数可以被非静态函数访问,但是它只能访问静态的函数和数据
const关键字:
C++的顶层const和底层const
// 顶层const,表示b1是常量
const int b1 = 20; int const b1 = 20;
// 顶层const,表示b1是常量
int a = 10;
int *const b2 = &a;
// 底层const,b3可变,但是指向的对象不可变
const int *b3 = &a;
// 底层const,引用变量不可变
const int &b5 = a;
数组名和指向数组首元素的指针区别?
final和override关键字
final:修饰类名表示该类不会被继承,修饰虚函数表明该函数不能被重写。如果被继承或重写,编译器会报错
class Base
{
virtual void foo();
};
class A : public Base
{
void foo() final; // foo 被override并且是最后一个override,在其子类中不可以重写
};
class B final : A // 指明B是不可以被继承的
{
void foo() override; // Error: 在A中已经被final了
};
class C : B // Error: B is final
{
};
override:声明该函数时重写的父类的虚函数,如果不是父类函数会报错,防止写错
class A
{
virtual void foo();
};
class B : public A
{
virtual void f00(); //OK,这个函数是B新增的,不是继承的
virtual void f0o() override;
//Error, 加了override之后,这个函数一定是继承自A的,A找不到就报错
};
用于类对象的直接初始化和拷贝初始化
详细解释的博客
直接初始化:直接调用与实参匹配的构造函数,通常是"( )"赋值的形式
拷贝初始化:总是调用拷贝构造函数,会先调用构造函数创建临时对象,后调用复制构造函数用改临时对象初始化,通常是" = "赋值的形式
string str1("I am a string");//语句1 直接初始化
string str2(str1);//语句2 直接初始化
string str3 = "I am a string";//语句3,拷贝初始化
string str4 = str1;//语句4,拷贝初始化
extern "C"的用法
// 1. C++调用C函数
//xx.h
extern int add(...)
//xx.c
int add(){
}
//xx.cpp
extern "C" {
#include "xx.h"
}
// 2. C调用C++函数
//xx.h
extern "C"{
int add();
}
//xx.cpp
int add(){
}
//xx.c
extern int add();
野指针和悬空指针
nullptr,这样在使用时编译器会报错,避免非法访问类型安全
C++中重载、重写(覆盖)和隐藏的区别
C++有哪几种的构造函数
#include
using namespace std;
class Student{
public:
//1. 默认构造函数,没有参数
Student(){
this->age = 20;
this->num = 1000;
};
// 2. 初始化构造函数,有参数和参数列表
Student(int a, int n):age(a), num(n){};
// 3. 拷贝构造函数,参数是对象
Student(const Student& s){
this->age = s.age;
this->num = s.num;
};
// 4. 移动构造函数,参数是右值引用
Student(const Student&& s){
this->age = s.age;
this->num = s.num;
};
// 3. 转换构造函数,形参是其他类型变量,且只有一个形参
Student(int r){ //
this->age = r;
this->num = 1002;
};
~Student(){}
public:
int age;
int num;
};
浅拷贝和深拷贝的区别
内联函数和宏定义的区别
public,protected和private访问和继承权限的区别
修饰变量和函数:
权限继承:
派生类对基类的访问
如果用代码判断大小端存储
大端存储:字数据的高字节存储在低字节
小端存储:字数据的低字节存储在低地址中
注意:在Socket编程中,往往需要将操作系统所用的小端存储的IP地址转换为大端存储,这样才能进行网络传输
volatile、mutable和explicit关键字的用法
volatile关键字
mutable
explicit
什么情况下调用拷贝构造函数
- 通常是在类使用其他对象初始化、作为函数参数传递或者函数返回值是类对象时,但是g++中函数返回局部对象不会引发拷贝构造