c/c++面试踩坑笔记
各种类型内存占用
分32位和64位
32位(内存字节)
char(1)
short(2)
int(4)
long(4)
long long(8)
float(4)
double(8)
long double(8(vs)/12(gcc))
size_t/ssize_t(4)
所有指针(4)
64位
char(1)
short(2)
int(4)
long(8)
long long(8)
double(8)
long double(8(vs)/16(gcc))
size_t/ssize_t(8)
所有指针(8)
以上所有无符号和有符号占用一样
堆和栈区别
堆和栈都是在内存上面分配空间,区别:
管理方式
栈:系统分配内存,自动回收
堆:手动分配,手动释放
内存大小
栈的内存空间小,固定受限
堆的空间大,几乎受限于整机内存大小
分配方式
栈:连续内存,向下增长
堆:零散内存,向上增长
访问速度
栈快,堆慢
生命周期
栈随函数调用/结束自动存亡
堆由代码控制生命周期
cin,get,getline区别
cin
c++的输入函数,读取输入,遇到空格或者\n就会停止比如
hello world,就只会读取到hello,末尾有\n会保留在缓冲区中。
get
无参的时候读取单个字符,包括换行空格。有参的时候读取一行,遇到\n停止,\n保留在缓冲区。
getline
读取一行,遇到\n停止,\n会读出来丢掉
getline(cin,str)
c++专属,整行读取,不限定长度,string自动扩容,遇到换行停止,\n自动丢弃。
get和getline区别
就是\n,get会不动他,getline会读取出来丢掉
坑点
cin和get/getline混用的时候比如:
char str[100];
cin>>a;
cin.get(str,100);
cin了以后缓冲区中会残留\n,get或者getline看到\n会直接停止了,那么怎么解决这个问题呢,书上是在cin了以后调用一次get把\n吃掉,我网上查还有用ignore清掉残留。
string内存坑点
string是c++的变量类型,string的结构是指针+容量+大小+内部短字符缓冲区。
sizeof测量的是string对象本身的大小,64位sizeof(str)=32字节,32位sizeof(str)=24字节,与存放的字符串长度无关。如果字符串<=15个字符,会存放到对象本身,如果>15个字符,会额外在堆上开辟空间存储len+1,但不影响sizeof(str)。
这里又区分c和c++
c的字符串char* p和char p[]
sizeof(p)=8,因为p是指针,不是内容,测量的指针在64位中就是8字节
char p[]="abc",数组大小=len+1,字符串末尾会有\0,len是字符个数。
c++的string操作更方便,c语言的两个字符串之间不能直接赋值,类似于数组,要循环加入,或者cstring中的使用strcpy复制或者使用strcat附加实现。而string支持直接赋值,拼接,附加。
另外size和length函数没有区别,返回的都是字符个数(去掉\0后的)
结构体坑点
c和c++的结构体差不多,但是有区别,c++的结构体可以放string类型(c++的专属)以及c++的结构体可以放成员函数,但是放了成员函数就和类没啥区别了。
枚举量坑点
枚举量enum只有赋值运算符没有算数运算符如++,但如果运算的值是有效的可以强制转换
常指针与指针常量
类型用模板T代表
指向常量的指针,用 const T *p或T const *p表示,是一个指针指向一个常量,指针的地址可以改变,但是他所指向的地址存的值不可以改变。
常指针,用 T *const p表示,表示常量指针类型,指针指向的地址不可以改变,但是指向的地址所存的值可以改变
指向常量的常指针,用 const T *const p表示,这种指针指向固定地址,并且该地址存的值也是固定不变的。
回调函数
我个人其实不喜欢用回调函数,我个人更喜欢用qt的信号槽机制,那个更容易看懂,但难免还是有要用到的地方。
常规回调函数
说白了就是将一个函数指针传入另一个函数,main里面运行的时候运行到这一步只会调用一下他就继续往下走,满足条件才会执行这个函数
#typydef void(*callback)() //定义函数指针类型,名字叫callback
//回调函数
void func(){
std::cout<<"回调函数执行完毕"<<std::endl;
}
void run(callback cb){
cb();//触发回调
}
int main(){
run(func);
}
注意传入的是函数指针,传入的函数带上()就执行了。
c++11的std::function函数回调
这个就好看多了,用function<void(int)>代替typedef void(*callback)(int)
#include<iostream>
#include<functional>//function头文件
void task(function<void(int)> cb){
cout<<"任务执行完毕"<<endl;
cb(200);
}
//普通函数
void run(int n){
cout<<"普通函数回调"<<n<<endl;
}
int main(){
//方案一
task(run);
//方案二
task([](int n){cout<<"lamda回调"<<n<<endl;});
}
这里面新增了lamda方案,方案一是和常规的一样调用。方案二相当于是把run函数省略直接写到lamda的执行部分了,task函数可以看见有一个int的传参,会自动赋值给lamda的参数位,给lamda当实参使用。
类成员函数做回调
坑点:类的普通成员函数有隐藏this指针,类型和普通的函数指针不匹配。
情况一:静态成员函数
静态成员函数没有this指针,可以和常规一样调用。
class demo{
public:
static void staticcb(int val){
cout<<"静态成员函数"<<val<<endl;
}
};
void run(function<void(int)> cb){
cb(200);
}
int main(){
run(demo::staticcb);
return 0;
}
情况二:类的普通成员函数
用std::function+std::bind绑定对象this指针
#include<iostream>
#include<functional>
using namespace std;
class demo{
public:
void menbercb(int n){
cout<<"普通成员函数"<<n<<endl;
}
};
void run(function<void(int)> cb){
cb(200);
}
int main(){
demo d;
auto cb=bind(&demo::menbercb,&d,placeholders::_1);
run(cb);
return 0;
}
bind的第一个参数是成员函数地址,第二个参数是类实例也可以理解为对象,第三个参数是参数占位符,代表回调传入的第一个参数。
lamda介绍
//lamda格式
[捕获列表](参数)mutable->返回值{函数体}
常用捕获:
[ ] 不捕获任何变量
[&] 引用捕获所有局部变量
[=] 按值捕获所有局部变量
[var] 仅值捕获var
#define和#typedef区别
#define是给一个已有的常量取别名
格式:#define 别名 类型
#typedef是变量定义语法
格式:
typedef int myint;//给int取一个别名叫myint
typedef int* INTPTR;//指向int类型的指针
INTPTR P;//等价于int* p
typedef int ARR_INT[5];//定义一个int类型长度为5的数组类型,别名为ARR_INT
ARR_INT arr;//等价于int arr[5]
arr[0]=1;
区别:
1、typedef只能给数据类型取别名,不能给字面量,变量,数字取别名,给字面量,变量,数字取别名用#define
2、typedef是类型别名,define是单纯的文本替换,比如:
typedef int* a;
#define b int*;
a a1,a2;
b b1,b2;
前者typedef定义的a1、a2都是int*类型,后者define定义的b1、b2就不一样,等价于int *b1,b2,b1是int*,b2只是int。这点要注意。
更多推荐



所有评论(0)