一.C/C++/java的区别
1.1972年D.M.Ritchie研制成功C语言,它是一种面向过程的底层语言,按照逻辑顺序运行。1983年Bjarne博士(C++之父),它是一种面向对象的语言,具有继承、多态、抽象、封装的特性,易扩展,但性能比C低;
2.C++除malloc/free外还有new/delete关键字;C只有struct而C++还有class和public;C++不支持重载函数,有引用,常用于游戏开发(unity3d,unreal)、服务器(微软)、图像处理(openCV,OpenGL)、软件开发(wps)、深度学习(Tensorflow)、区块链(BTC、ETH)等;C主要用于基于C额操作系统和嵌入式领域
3.java上手简单,有完善的内存管理机制和异常处理机制,一次编译可跨平台运行
二.命名空间
对全局实体加以域的限制从而合理解决命名冲突声明一个命名空间时,大括号内可以存放变量、常量、函数、结构体、模板、类、命名空间。命名空间中实体的作用域是全局的,可见域是从实体创建到该名称空间结束。使用方式有以下三种:
#include <iostream>//c++头文件用模板编写的,而模板必须知道所有实现才能正常编译
1.using编译指令:将命名空间的全部十条一次性引入到程序中,有可能发生重定义错误
using namespace std;
2.作用域限定符
std::cout<<wd::num<<std::endl;
3.using声明机制:一次只引出一个实体
using std::cout;
1.匿名命名空间:空间中的实体只在本文件中有效,相当于static;使用时不必用命名空间限定,如::num;
2.两个命名空间可以相互调用、嵌套
三.const关键字
1.修饰变量
const int num=10;//const关键字修饰的变量成为常量
int const num1=5;//定义时必须初始化,后续不可更改以保护数据
const常量与宏定义的区别:(1)编译器处理方式不同。宏定义在预处理阶段展开,做字符串的替换,如果有bug运行时才能发现;而const是在编译时。(2)类型和安全检查不同。宏定义没有类型不做任何检查;const常量有具体的类型,编译时执行类型检查
2.修饰指针
const int *p1=&num1;//常量指针,不可以改变内容,可以改变指向
int const *p2=&num2;//常量指针,可以改变指向,不可以改变内容
int * const p3=&num3;//指针常量,不可以改变指向,可以改变内容
const int * const p4=&num3;//const指针常量,二者皆不可改
数组指针:指向数组的指针,int(*p)[8]表示指向有8个int类型元素的数组的指针;
指针数组:存放指针的数组,int* p[8]表示该数组的每一个元素都是int类型的指针;
函数指针:指向函数的指针,int (*func)(int x)包含了函数地址,可以用来调用函数;
指针函数:返回指针的函数,int\ * func(int x)返回int类型指针;
#include <stdio.h>
int sum;
int *add(int a,int b){//指针函数
sum=a+b;
return ∑
}
int add1(int a,int b){
return (a+b);
}
int main(){
int *p;
p=add(1,2);
int (*p1)(int,int);//函数指针
p1=add1;
printf("add=%d,add1=%d\n",*p,p1(3,4));
int a[2][5]={{1,2,3,4,5},{6,7,8,9,10}};
int (*p)[5]=a;//a=&a[0],区分数组首地址与数组地址,区分二维数组与数组指针
int b[3]={1,2,3};
int (*p)[3]=&b;//或者int *p=b;保持类型一致
}
3.修饰对象与成员函数
const对象不能调用非const成员函数;const数据成员值不能修改,只能在初始化列表中初始化;const成员函数不能修改其数据成员(见类和对象章节),可以被非const对象调用
三.new/delete表达式
int *p=new int(1);
delete p;//开辟一个元素空间
int *p=new int[5]();
delete []p;//开辟一个数组空间
int *p=(int*)malloc(sizeof(int)*5);
memset(p,0,sizeof(int)*5);
free(p);
1.new/delete和malloc/free的区别和联系:(1)malloc/free是c语言的标准库函数;申请堆空间时没有初始化;申请空间时需要传入参数;(2)new/delete是C++的关键字;自动分配空间大小,申请堆空间时进行了初始化操作;还能对对象进行构造和析构函数的调用进而对内存进行更加详细的工作;(3)都需要成对出现否则会造成内存泄漏,都可以申请堆空间(4)由于C++程序经常要调用C函数,而C程序只能使用malloc/free管理动态内存,因此C++仍然保留了malloc/free
2.malloc的底层实现原理:(1)malloc维护一个空闲内存块链表,每个链表节点都包含一个进程或者是进程间的空闲内存。通过首次适配、下一次适配、最佳适配找到合适的位置放置。
(2)linux系统通过系统调用brk()和mmap()分配虚拟内存空间。brk是将数据段(.data)的最高地址指针_edata往高地址推,而brk()分配的内存需要等到高地址内存释放后才能释放(产生碎片,空闲内存大于128k时内存紧缩)。mmap是在进程的虚拟地址空间(栈和堆中间,文件映射区)中找一块空闲的虚拟内存;
(3)当空闲链表上没有可以分配的内存时,(1)当开辟的内存空间小于128K时,系统调用brk()函数,使用_edata指针向高地址推进一片空间(只分配虚拟内存,不对应物理内存,因此没有初始化,读写数据引起内核缺页中断时才分配物理内存);大于128K时,调用mmap()在进程的虚拟地址空间(即文件映射区,堆和栈中间)由高地址向低地址找一块空闲的虚拟内存。
3.free是如何释放内存的:(1)new分配内存,调用构造函数;delete调用析构函数然后释放内存;(2)free文件映射区空间时,对应的虚拟内存和物理内存一起释放,free堆空间内存时,对应的虚拟内存和物理内存都没有释放(因此释放后应该把指向这块内存的指针指向NULL防止程序后面不小心使用了它),当最高地址空间的空闲内存超过128K时,内存紧缩(3)free释放指针指向的内存并不释放指针变量,指针指向未定义的垃圾内容(野指针)
void free(void *firstbyte){
struct mem_control_block *mcb;
//指针回退struct个字节,取地该内存控制块的首地址
mcb=firstbyte-sizeof(struct mem_control_block);
mcb->is_available=1;//将该块内存标记为可用
return;
}
4.区分概念内存泄漏、内存溢出、内存越界、内存踩踏、野指针:内存溢出指程序申请内存时空间不足;内存泄漏指程序未释放或无法释放已申请的内存造成程序运行速度减慢甚至系统崩溃,内存泄漏最终会导致内存溢出;内存越界指存储的内容大小超过申请的空间大小;内存踩踏指内存重叠即拷贝的目的地址在源地址范围;野指针指指向一个已删除的对象或未申请访问受限内存区域的指针(指针未初始化,指针释放后未置空、指针操作超越变量作用域如指针指向栈内存)
四.引用
1.引用与指针的异同点
(1)相同点:指针和引用都是地址的概念----指针的内容是所指内存的地址,引用是某块内存的别名;引用和指针都占内存空间;都可以优化传参效率;
(2)不同点:指针可以为空可以不初始化可以有多级,引用不可以为空不可以不初始化只有一级;指针的值初始化后可以改变,引用的底层实现是指针常量,一经初始化不能再改变指向;指针指向自己的一块而引用仅是个别名:引用直接访问变量不用分配自己的内存空间,指针是间接访问需要有自己的内存空间;引用没有const,指针有const;“sizeof 引用”得到所指向变量的大小,而“sizeof 指针”得到指针本身的大小;指针和引用自增运算意义不同;作为参数传递时,指针需要解引用才可以对对象进行操作,而直接对引用对象的修改会改变引用所指向的对象
2.引用作为函数参数
特点与值传递、指针传递比较:直观、对形参变量的操作就是对实参本身的操作,不需要为形参开辟额外的空间,不需要进行复制,提高了程序执行效率
void swap(int& x,int& y){
int temp=x;
x=y;
y=temp;
}
3.引用作为函数返回值
注意:(1)不要返回局部变量的引用,因为局部变量在函数返回后会被销毁,这时被返回的引用“无所指”;(2)不要返回函数内部new分配的堆空间变量的引用;返回一个临时变量的引用会造成内存泄漏(3)实体的生命周期一定要大于函数的生命周期
int &func1(){
temp=10;
return temp;//返回时不对temp进行复制
}
4.常引用
如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,使用常引用。
int b=3;
const int &a=b;//等价于const int *const a=b,对象地址和值都不可修改
//被const修饰的对象的引用必须是常引用
const int b=3;int &a=b;//错误
const int &a=3;//常引用可以使用相关类型对象初始化
五.强制转换cast
1.const_cast可以去掉变量的const属性,使常量可以修改
2.static_cast:用于类层次结构中基类和子类之间指针或引用的转换,上行转换是安全的,下行转换由于没有动态类型检查不安全;用于基本数据类型间的转换
3.dynamic_cast:可以安全的下行转换,只能用于含有虚函数的类。指针类型转型失败时返回NULL,引用类型转型失败抛出bad_cast异常
4.reinterpret_cast:重新解释转换,将值强行赋值,不检查
int inum=1;
float fnum=2.34;
inum=int(fnum);
inum=(int)fnum;//C风格强制转换,简单粗暴,不方便查找
inum=static_cast<int>(fnum);//1.基本数据类型之间的转化
void *pret=malloc(sizeof(int));//2.void*->目标类型
int *pInt=static_cast<int*>(pret);
//3.基类到子类之间指针或引用的转换
const int num=10;
int *p=const_cast<int*>(&num);//将常量转换成非常量
六.函数重载和默认参数、bool类型
在同一作用域内,可以有一组函数名相同形参列表不同的函数,称为重载函数。C语言不支持函数重载,而C++支持,原理是进行了名字改编(name magling),函数名字相同时,编译器根据参数个数、顺序、类型进行改编(nm *.o查看)。作用:减少函数名数量,避免名字空间的污染
//C和C++混合编程--使C的源码能同时在C和C++中以C的形式进行调用
#ifdef _cplusplus//C++内置宏
extern "C"{//C++兼容C的库,按照C的方式进行编译--不名字改编
#endif
int add(int x,int y){
return x+y;
}
#ifdef _cplusplus
}
#endif
默认参数的目的:少些代码。传参顺序:从右向左。最好声明时写默认参数,定义时写则函数调用在定义之后。注意函数重载的二义性。
bool类型:非零值为true,零为false,占用内存空间大小为1
如何判断一段程序是由C编译程序还是由C++编译程序?
1.用nm查看函数入口,c++程序支持函数重载,c程序不支持函数重载;2.c程序中_STDC被定义,c++程序中_cplusplus被定义;3.c++使用extern "C"兼容c的库,按照c的方式编译
七.inline函数
1.普通函数,inline函数与带参宏定义的区别?
(1).普通函数–编译时调用函数带来很大的函数开销;
(2).C语言中使用带参宏定义–预处理时,用宏定义的字符串替代宏名。借助编译器的优化技术减少程序执行时间,没有函数开销但是有安全隐患;无法调试;无法操作类的私有成员
(3).C++采用inline函数–编译阶段,编译器用函数的定义体替代函数调用语句并进行类型和安全检查,消除了局限性和隐患。函数,具有函数的基本性质,可以调试。作为类的成员函数时可以访问类的所有成员,而this指针也会被隐式的正确使用。在类中声明时定义的成员函数自动转化为内联函数
2.内联函数放入头文件
内联函数适用于:函数代码少,频繁调用;短函数,函数体中无for/while/switch等;
#ifndef __ADD_H__
#define __ADD_H__
/*#define add(a,b) a+b;*/
inline int add(a,b){//inline函数的声明和实现必须放在一起;
return a+b;
}
/*#include "add.cc";*/不放在一起时
#endif
八.异常安全
异常提供了一种转移程序控制权的方式
try{
throw 表达式;
//语句块,若没有抛出异常,不匹配catch
}catch(异常类型){
//具体的异常处理
}
九.字符串
C风格字符串常用库函数有:strlen、strcmp、strcpy、strncpy、strcat、strstr(查找子串)、strchr(查找字符)易引发程序漏洞;C++提供string类用于字符串处理,不必担心内存、空间问题、结尾字符,string类本质是basic_string类模板关于char型的实例化.
参考网站:cppreference.com/zh.cppreference.com
string s3=s1+s2;//字符串拼接
string s3=s1+'A';//字符串拼接
s1.append(s2);//拼接
size_t pos=s3.find(s2);//查找
string s4=s3.substr(pos,5);//截取
const char* pstr=s3.c_str();//c++风格转为c风格字符串
size_t len=s3.length();len=s3.size();//获取长度
for(auto &elem:s3){cout<<elem<<" ";}//遍历
十.内存布局 (自高地址到低地址)
对32位操作系统,寻址空间是4G。Linux默认将高地址的1g空间给内核(从0xC0000000开始),其余3g是用户态空间。从高地址到低地址有以下默认区域:
1.栈:用于维护函数调用的上下文,存放函数参数和局部变量等;向下增长;编译器自动分配;
2.内存映射段:内核将硬盘文件映射到内存,高效的文件I/O,装载动态库;
2.堆:应用程序动态分配内存;向上增长;只能通过指针间接访问;末端由brk指针标识;
3.可执行文件映像:存储着可执行文件在内存的映像,加载器将可执行文件的代码和数据映射到这里。包括读写段(也叫静态存储区.data .bss)和只读段(.init .text .rodata)。
3.1 .bss段:未初始化的静态变量和全局变量,填充0;
3.2 .data段(数据段):由程序员初始化的静态变量和全局变量;
3.3 .rodata段:存放程序中的常量值如字符串常量
3.4 .text(代码段):存放程序中的可执行代码(从0x08048000开始);
4.保留区:内存中受保护而禁止访问的区域,无物理地址
栈与堆的比较?
1.管理方式:栈由编译器自动管理,速度快;堆由程序员控制,使用方便但容易内存泄漏,速度慢且容易产生内存碎片;
2.生长方向:栈向低地址扩展,是连续的内存区域;堆向高地址扩展,不连续(linux系统用链表存储内存空闲地址);
3.空间大小:栈顶地址和最大容量由系统预先规定,堆的大小受限于有效虚拟内存;
4.存储内容:栈在函数调用时首先压入主调函数中下条指令的地址然后是函数实参、被调函数的局部变量,调用结束后局部变量先出栈然后是参数最后栈顶指针指向最开始的内存地址继续运行;而堆的头部用一个字节存放堆的大小,存放new创建的对象
5.分配方式:堆是动态分配,栈是静态分配由编译器完成;
6.分配效率:栈由计算机底层提供支持,分配专门的寄存器存放栈地址,压栈和出栈由专门的指令,因此效率高;堆由函数库提供,机制复制,效率低
int a;//全局变量,初始化位0,.bss段
int *p;//全局变量,初始化为null
void func(){
printf("hello");
}//代码段.text
int main(){
int b;//栈区,随机值
char *p1;//栈区,野指针
static int c=10;//静态区,.data
char *pstr="12";//pstr位于栈,字符串常量位于文字常量区.rodata
char *p3=new char[10]();//p3位于栈区,指向堆区
}
例题
1.在一个类中可以对一个操作符进行(多)种重载
2.在(CE )情况下适宜采用inline定义的内联函数?
A.函数体有循环语句 B.函数体有递归语句
C.函数代码少、频繁调用 D.函数代码多、不常调用 E.需要加快程序执行速度