个人总结的c++基础语法,有一些可能不重要…
int main(int argc, char argv):main函数也可以命令行传参;argc表示参数个数,argv表示所有的参数,可以用getopt函数解析:
main函数执行之前后可能执行的代码是什么:创建全局对象,调用构造函数,之后全局对象的析构。
面向过程和面向对象的区别:面向过程:分析解决问题的步骤,然后一步一步函数实现。效率高,但难维护,复用性低。面向对象:把问题分解成各个对象来解决,每个对象有自己的方法和属性。便于维护,复用性高,但开销大。
变量的定义和声明的区别:声明就是告诉编译器变量存在,不会分配内存,可多次声明,定义会给变量分配内存,只能定义一次。
c和c++区别:c面向过程,c++面向对象;c++多一些关键字如new,delete取代malloc,free;c++支持重载,虚函数,引用。
C++中struct和class的区别:默认权限不同以及不能定义模板,其他基本可以互用。但与c的struct不同,c的struct作为一个数据集合,无成员函数,没有访问权限
全局变量和局部变量区别:全局变量存储在全局区,生命周期与程序一样,作用域是全局的,在编译时分配内存;局部变量存储在栈区,函数调用完就消失,作用域在函数内部,调用时才分配内存。
ifndef:预处理指令,检查一个宏是否未被定义,#elif, #else #endif;一般用于通过宏来设置编译不同的代码。而头文件包含用#progma once
static:修饰变量和函数:
那静态成员函数如何实现访问非静态成员:
1、既然他没有this指针,那我们手动传一个对象做形参
2、函数内部创建对象来访问。
const:用于修饰变量或成员函数。
inline:修饰函数成为内联函数,编译时把函数调用的表达式替换成函数代码,避免了运行的函数调用的跳转开销,但耗内存,增加编译时间,所以要权衡。一般用于频繁调用的小型函数或短循环。内联声明只是建议,最终还是由编译器决定。
define:预处理指令:宏定义,预处理阶段进行文本替换,无类型检查,不遵守作用域规则的,因为在预处理阶段就替换了,所以慎用。const是在编译阶段定义常量,有类型检查,会分配内存。
定义长宏:如日志宏要替换成一个函数,使用do while(0),举例:
因为我们使用这种函数宏时会在后面加分号,while(0)正好可以接受分号。
typedef:定义一个类型,如:
typedef,inline和const都在编译阶段执行,且都有类型检查;
volatile:我们知道编译器为了加快代码运行速度,会把内存的数据拷贝到寄存器,后续对变量的读取或者修改都通过寄存器这个中间人即cpu-寄存器-内存,这会导致内存变量和实际变量值不一致。加上volatile编译器就不会优化了,直接cpu-内存。它的作用:保证变量的可见性,对变量的修改所有线程会立即可见;保证代码执行的有序性:禁止指令重排序;
但不保证原子性(多线程共享变量,一个++,一个读取),所以需要保证原子性不能用它。举例:当用一个变量的改变标识程序的启动就需要可见性。当需要代码执行有序时a=1,b=2;
extern:对变量或函数声明,以便使用他们。
你想使用别人源文件里定义的变量或函数,要么包含对应的头文件,头文件里extern了。不然访问不了。函数的声明可以省略extern
extern “C”声明下面引用的代码按c来编译,因为c++有重载,编译时会带上形参,c编译时不会带上形参,只包含函数名,作用就是让c++代码兼容c代码。注意:static修饰的全局变量或函数无法extern,它限制了可见域。
只有c++用到库代码采用extern “C”, 如:
explicit:用于修饰构造函数,表示只能显示调用构造函数,如:
expilict test(const test& t) {}
A a; A b = a;这个隐式构造就不行
final:放在类名后面则类不能被继承;放在虚函数名后面则虚函数不能被重写。
override:显式表明该函数是重写父类的虚函数,会进行编译检查。
数组名与指向数组首元素的指针:数组名在sizeof和&操作与指针不同之外,可认为是个指针常量,指向首元素地址即a[0]&ap[0];但它作为形参就会退化成一般指针。
程序编译的4个阶段:
- 预处理:处理以#开头的预编译指令,如宏替换、头文件包含,得到预处理后的源代码
- 编译:语法检查,把源代码转换成汇编代码
- 汇编:把汇编代码转换成二进制代码即机器指令,生成二进制文件
- 链接:把所有目标文件链接到一起生成最终的可执行文件。
静态绑定和动态绑定:静态绑定在编译时根据变量类型确定要调用哪个方法,动态绑定在运行时根据对象类型确定调用哪个方法,只有虚函数才是动态绑定。
为什么只有引用和指针能实现动态绑定即动态多态:因为指针或引用在编译时不关心所指对象的类型,只要与指针或引用指向的类型兼容就行,而父类和子类类型是兼容的。所以这就使得父类的指针或引用可以在运行时根据所指对象类型动态绑定,而直接赋值在编译期就确定类型了。
静/动态链接:
就是静/动态库的区别:
- 静态库:链接时把代码复制到对应的文件里了,运行时程序不依赖静态库。性能高,但耗内存,更新不便
- 动态库:链接时只把库的入口加载到程序中,应该叫dll,运行时时再调用具体函数。所以它内存占用小,性能慢一点,更新方便
如何查看程序依赖哪些动态库:ldd https://blog.csdn.net/_/article/details/a.out
指针和引用:
1、 指针就是一个变量类型,他与int创建的变量没有区别,只不过存的值是地址;
2、引用是变量的别名或者叫小名,就是原变量,引用依附于原变量存在,所以必须在创建时就要绑定变量。
三种传参:
值传递、指针传递、引用传递:
1、 值传递:形参拷贝实参,互不影响,一般用于不想改变实参的小型 数据。
2、 指针传递:本质也是值传递,拷贝的是指针,但可以通过解引用得到变量地址进行修改,所以可以认为指针传递间接改变实参值。
3、引用传递:形参就是实参别名,动形参就是动实参。
这2个如何选择:能用给引用传递就用引用传递(引用最初就是为了简化指针的写法),效率高(不需要解引用)、代码可读性高、不产生拷贝副本(指针还要拷贝8个字节呢)。但若要直接操作地址如链表,树,以及可能有空值的情况,用指针更好。
引用作为成员变量占内存吗: 占用,引用变量也是变量,本身是左值,大小等同指针8或4,要存储绑定对象的地址。
函数指针:指向函数的指针,因为函数也有地址,可用函数指针调用函数。语法int(*p)(int,int)=test;。用途:回调函数,把函数作参数:如创建线程时就得分配一个回调函数。
野指针和悬空指针:野指针是未初始化的指针;悬空指针:指向被释放了的内存的指针(浅拷贝易出现)。他俩都是不安全的,避免:养成初始化的习惯,delete后立即置空,使用智能指针自动管理。
++i与i++的区别:后置由于返回旧值,所以会调用拷贝构造;前置返回新值,所以直接返回的是引用。
strcpy和memcpy,snprintf:strcpy只能拷贝字符串,拷贝0后结束,memcpy拷贝的是内存,所以不限类型,需要指定长度;snprintf用于把格式化的数据写入字符串,如
strcpy:复制字符串到0,若接不下缓冲区会溢出,所以要手动注意目标字符串长度要大于源字符串。考虑内存覆盖:
void* mymemcpy(void* s1, const void* s2, int len) {//字节数;代码与strcpy相似。
void* memset(void* s, int c, int size) ;for循环赋值
memset也是直接操作内存,一般用于清结构体,类,但注意若类里有虚函数表会破坏虚函数表,因为虚表指针被清了。
strncpy:strcpy是一直拷贝完0结束,有缓冲区溢出的风险,strncpy指定拷贝n个字符,相对安全,要手动加0。
strlen和sizeof:sizeof是运算符,求任意类型/数据的大小,求字符串时会包括0,strlen是库函数,用于求字符串长度,不包括0
void*:void做形参,可接受任意类型指针/地址,void做形参,要传一级指针的地址再转(void),他俩在函数内部都要转换成具体类型再操作。
RTTI:运行时类型信息,依赖虚表实现。2个机制:typeid运算符:获取对象类型;dynamic_cast:用于安全转换。
C++四种强制转换:C的强制类型转换没有类型检查,所以C++引入4种,会做类型检查,避免明显的错误。
1、static_cast:用于基本类型、子类指针转父类指针,且父类指针转子类指针不要用它(没有动态类型检查);
2、 const_cast:去除指针的const属性
3、 reinterpret_cast: 不做任何检查,任意类型的指针直接转,要谨慎 使用。
4、dynamic_cast:用于虚基类指针转子类指针,失败返回nullptr,它在运行时有类型检查。为啥一定是虚基类:因为它是RTTI的体现,RTTI依赖虚表。
个人感觉:这4个不好用,要转换:直接(double)a;
且形参若是void,则可接受任意类型的指针。
fork,wait,exec函数:fork函数用于拷贝父进程的一个副本创建子进程,在父进程中返回子进程的进程ID,在子进程中返回0。读时共享一块内存,写时才拷贝;wait函数用于父进程阻塞等待子进程的终止。exec函数在当前进程执行一个新程序,并取代进程内容。
cout 和printf:printf(const char*, …),输出会解析字符串,遇到%就从可变参数中获取相应的值替换%最后输出。cout其实是ostream类的一个实例,使用cout会创建一个输出流对象,使用<<把数据插入到对象里,cout<<可以输出各种类型是因为<<已经重载了各种类型。他俩输出都是先输出到缓冲区在输出到屏幕,但c++有endl手动刷新缓冲区。
若想输出自定义类型如类对象需要重载<<:
判断2个浮点数是否相等:abs(a-b)<=定义的误差
函数调用的过程:
首先我们要知道每次函数调用都会产生一个栈帧,保存函数调用的一些信息,如形参,局部变量,返回地址,寄存器,可以认为栈帧存储了函数调用的上下文。假设现在有个程序启动了,操作系统会分配一个栈给他,到了main函数:创建main栈帧,入栈;执行f1函数,创建f1栈帧入栈。而程序的执行上下文栈顶决定的,所以它来到了f1函数,执行完把返回值返回,f1栈帧出栈,程序回到main栈帧执行的上下文。
this指针:是非静态成员函数的隐式形参,谁调用函数,this就指向那个对象,本身是常指针T*const this。成员函数为啥能直接访问成员变量就是隐式调用了this指针。一般用于:返回对象本身,可return *this; 还有就是区分形参名和成员变量名。在成员函数中调用delete this就是释放对象内存。在析构函数delete this会栈溢出,因为delete this又会调用本对象的析构,就无限递归。
C++模板:泛型编程技术,允许编写代码时使用参数化类型,调用时按类型匹配,提高代码复用性。模板主要有函数模板和类模板,模板类创建时时要显示指定类型。底层原理:编译器进行2次编译,先是对定义的模板代码进行编译,遇到模板调用时进行模板实例化确定具体类型,得到具体代码再进行编译。
偏特化:给特定的类型开个小灶,给他一种特定的实现。
类模板使用要显示指定:A<int ,string> pa(1, “ad”);
template
T Add(T a, T b) {return a + b;}
运算符重载:本质就是通过定义运算符的重载函数,实现自定义数据类型的运算,如对象+数字;
实现:成员函数(一般的±=()用成员函数就行,this绑定运算符左侧对象,所以传参数少写一个,具体几个参数和你的运算符一致)或者友元(重载输入输出运算符,这个背下来)
异常处理:通常用try,throw,catch,先执行try包含的语句,若throw会抛出异常try的代码停止,catch捕获处理,处理完后程序继续运行。
int main() {
int a = 1;
try {
if (a == 1) throw -1;
} catch(…) { //捕获所有异常
cout << “未知异常”;
}
cout << 2;
}
但一般我们不主动抛出异常:只会去try判断是否有异常,如:
左值右值: 能取地址就是左值,不能取就是右值。
引用类型:
1、左值引用:int& a = b;
2、右值引用: int&& a = std::move(b); int&& a = 1;
3、万能引用:T&&,做形参接收任意类型,一般配合函数内部的forward使用。
注意:不管是左值引用变量还是右值引用变量,都是别名,变量本身都是左值。
std::move原理:
就是用static_cast把变量转成右值。
移动语义:
举例:现在有一个int a,我要把他放到vector里,且之后a就不用了,以前肯定是会拷贝。现在有移动语义了,我们可以直接把a的资源移走来构造元素,省去了不必要的拷贝。
实现:一般通过std::move把对象变成右值引用,然后调移动构造
实际场景:如一个函数里,我创建了一个string,返回值就是返回这个string,那就可以用移动构造。不然你直接return string会调用拷贝构造。
完美转发:保证传参过程中左右值属性不变。
场景:给func函数传了一个形参, func内部用这个形参去调另一个函数test, test有左右值2个版本,我们希望形参是左值调左值函数,是右值就走右值函数。
实现:T&& 类型推导 + forward内部引用折叠
智能指针:
1、定义:是个模板类,遵循RAII原则,把资源的申请和释放放在了对象的构造和析构函数里,实现资源的自动释放,防止内存泄露。且它重载了->和*运算符,让我们使用它和使用普通指针一样。
2、创建指针语法:
3、三种类型:
(1)unique_ptr:独占指针,同一时间只能有一个指针指向该资源,内部原理:禁用了拷贝构造和拷贝复制。但可以移动(因为移动了还是只有独占的)。
实现:
(3)weak_ptr:弱指针,为了解决2个共享指针相互引用导致死循环的问题,A类有B类的智能指针成员,B类有A类的智能指针成员,创建2个智能指针让他们相互指向,即计数都为2,2指针不管谁先出作用域计数也只能减到1,无法释放,他俩互相等对方释放资源,陷入死循环,此时只需要把其中一个改为weak_ptr即可,因为weak_ptr不会导致计数增加,且他能通过lock()函数提升为shared_ptr访问资源,若为空说明资源被释放了。即达到了循环引用的目的又不会形成死循环。
4、unique_ptr与shared_ptr如何选择:基本都是用unique_ptr, 除非一个资源确实要在多个部分共享(如多线程共享某个类对象),才会考虑shared_ptr,因为他要维护计数,开销更大。
自定义删除器:
默认的删除器就是调用智能指针的析构,也可以自定义,在某些场景下,如连接池,连接用完不释放,放回队列,这就可以用。
auto:,必须要有初始值,一般用于比较复杂的类型;但有些情况下不能用:函数的形参、返回值,模板推导,以及多态对象(用auto肯定推导成子类的了)
delctype:返回表达式的类型,不会真正运行该表达式。
NULL和nullptr:nullptr只表示空指针,NULL即可表示空指针,又表示整数0。
Lambda表达式:
1、定义:就地创建函数对象供我们使用,而不用特意创建一个函数或者类。它还可以捕获外部变量使用。
2、底层原理:编译器把他当成仿函数,重载了函数调用运算符,所以它可以像函数一样调用。而它的变量捕获,如值捕获就是成员变量拷贝外部的值,引用捕获就是外部变量的引用。
3、常用于如自定义排序:sort(a.begin(), a.end(), [](auto a, auto b){return a > b;}); sort第3个参数接收一个函数对象,可以就地使用lambda表达式而不需要特地去定义一个函数了;优先级队列priority_queue<int ,vector, decltype(f)> q(f);优先级队列接收函数对象的类型,且初始化函数对象。以后就这么写。
4、个人理解:最多的用于匿名回调、作为函数对象传参。
什么是函数对象:能像函数那样被调用的对象(重载了函数调用运算符)
可变参数模板:普通的模板允许参数类型泛化,可变参数允许参数的个数泛化,可变参数模板2者结合:参数的个数、类型都泛化。
可变参数模板函数(代码背下来);接收任意类型和数量的参数。
//可变参数模板类,创建对象时可传入任意数量和类型的参数包,这里还打印了。
a与&a的区别:&a得到数组地址,&a+1会偏移一整个数组
assert:断言,若断言的表达式为假,会终止程序。如assert(x==5); release版本会把他禁用掉
namespace:命名空间,一定要多用,少用using namespace std
uint8_t: uint8_t、uint16_t、uint32_t、 uint64_t、int8_t等,以后多用这些,#include <iostream>
能用double不用float,常量默认就是double
enum:枚举,使用枚举加上类型,更安全,且结构体和枚举的定义不用加typedef:
结构化绑定:
用法1:遍历map
用法2:一次性初始化多个变量:
到此这篇ifstream函数(ifstream get函数)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/haskellbc/27740.html