C/C++基础知识总结
一、指针与引用的区别
1、指针:一个变量,存储的内容为一个地址;引用:给一个已有对象起的别名。
2、指针是一个实体,需要分配内存空间;引用知识变量别名,不需要分配内存空间。
3、可以有多级指针,不能有多级引用。
4、自增运算结果不一样。
5、指针是间接访问,引用是直接访问。
6、指针可以不用初始化,引用一定要先初始化。
二、指针与数据的区别
1、含以上的区别:数组对应着一块内存,而指针是指向一块内存。数组的地址和空间大小在生命周期不会发生改变,内容可能会发生改变,而指针指向的内存大小可以随时发生改变。当指针指向常量字符串时,它的内容不可以改。
2、计算容量的区别:用sizeof计算出数组的元素个数,无法计算指针所指向内存的大小。
3、数组名是常量指针,指针是变量指针。
4、对数组用&和对指针&的意义不同,此时数组名不在当成指向一个元素的常量指针来使用。
三、虚函数的数据结构
虚函数:用virtual定义的函数为虚函数。虚函数来源:基于C++的一个特性:子类能转换成父类,例如:
CBasic *parent; CBasic *p1; CChildren *child; parent = new CBsic; child = new CChildren; p1 = new CChlldren;
如上代码,p1为CBasic型指针,但实际对象是CChildren型,如果子类和父类有相同的函数时,是调用子类的函数还是父类的函数?基于这个问题,C++提出多态的概念:根据实际对象类型决定函数调用的具体目标,使用virtual关键字对多态进行支持。被virtual声明的函数被重写后具有多态性。
底层机制:虚函数是使用虚函数表和虚函数表指针实现的。虚函数表是一个类虚函数的地址,用于索引类本身和类虚函数,若子类重写父类函数,则会在相应的虚函数表处替换成子类虚函数的地址。虚函数表指针存在于每一个对象中,它指向对象所对应类的虚函数地址。
构造函数是不是虚函数并无多大影响,因为在构造子类一定要先构造父类。在存在继承并且析构函数需要用来析构资源时,析构函数一定要为虚函数,若使用父类指针指向子类,用delete析构函数时,只会调用父类析构函数,不会调用子类的析构函数,造成内存泄漏。
四、const与define的区别
1、编译器处理方式:const:编译时确定其zhi;define:预处理时进行替换。
2、类型检查:const:有数据类型,编译时进行数据检查;define:无类型,也不做类型检查。
3、内存空间:const:在静态存储区储存,仅此一份;define:在代码段区,每做一次替换就会进行一次拷贝。
4、define可以用来防止重复定义,const不行。
五、不用临时变量实现两个变量的交换
#include<iostream> using namespace std; void Switch(int *p1, int *p2) { *p1 = *p1 + *p2; *p2 = *p1 - *p2; *p1 = *p1 - *p2; } void Xor(int *p1, int *p2) { *p1 = *p1^*p2; //异或操作 *p2 = *p1^*p2; *p1 = *p1^*p2; } int main() { int a = 1, b = 2; int *p1 = &a; int *p2 = &b; count << "*p1 = " << *p1 << endl; count << "*p2 = " << *p2 << endl; Switch(p1, p2); count << "*p1 = " << *p1 << endl; count << "*p2 = " << *p2 << endl; Xor(p1, p2); count << "*p1 = " << *p1 << endl; count << "*p2 = " << *p2 << endl; system("pause"); return 0; }
方法一缺陷:相加和可能存在溢出情况。
六、函数指针与指针函数
函数指针:顾名思义,与整型指针类似,整型指针为指向整型的指针,函数指针为指向函数的指针,是指针变量,他与函数名无挂,只与参数列表和返回类型有关;
指针函数:本质是函数,返回值为一个指针。
#include<iostream> using namespace std; //求和函数 int ADD(int a, int b) { return a + b; } //求差函数 int Sub(int a, int b) { return a - b; } //求和函数 int* add(int *p1, int *p2) { int a = *p1 + *p2; int *p = &a; return p; } int main() { int a = 5, b = 3; int *p1 = &a; int *p2 = &b; //声明一个函数指针,只能指向两个整形参数,返回值为Int型的函数 int (*hanshuzhizhen)(int, int); hanshuzhizhen = ADD; //函数指针初始化,是函数指针指向ADD函数; count << "函数指针指向ADD函数计算结果:" << hanshuzhizhen(a, b) << endl; hanshuzhizhen = Sub; //函数指向Sub函数 count << "函数指针指向Sub函数计算结果:" << hanshuzhizhen(a, b) << endl; count << "指针函数计算结果:" << *add(p1, p2) << endl; system("pause"); return 0; }
七、一个C++源文件从文本到可执行文件经历的过程
1、预处理:对所有的define进行宏替换;处理所有的条件编译#idef等;处理#include指令;删除注释等;bao#pragma。
2、编译:将预处理后的文件进行词法分析、语法分析、语义分析以及优化相应的汇编文件。
3、优化:
4、汇编:将汇编文件转换成机器能执行的代码。
5、链接:包括地址和空间分配,符号决议和重定位。
八、C++11新特性
1、nullptr代替NULL,传统C++在识别NULL有两种情况,一种是空指针,一种是当做0,在重载时往往把应该看成指针的当做0处理。
2、类型推导:auto 和decltype(可以让编译器找出表达式的类型)。
3、区间迭代,使c++的for语句能向python一样便捷。
4、初始化列表。
5、模板增强。
6、新增容器 :std::array,std::forward_list(单链表)。
7、正则表达式。
8、线程支持。
9、右值引用(重点)。
九、C++和C的不同
1、c语言是面向过程的程序设计,主要核心为:数据结构和算法,具有高效的特性。对于C语言程序的设计,主要是考虑如何通过一个过程,对输入进行处理得出一个输出。C++是面向对象的程序设计,对于C++,首先考虑的是如何构造一个对象模型,让这个模型配合对应问题,这样可以通过获取对象状态信息得到输出。
2、C++比C语言的增强点:1、命名空间;2、实用性加强;3、register关键字;4、变量检测加强;5、struct 加强。
十、malloc的原理
函数原型:void malloc(size_t n)返回值额类型为void,为动态分配得到的内存,但代大小是确定的,不允许越界使用。
malloc函数的实质体现在它有一个可以将可用内存块连接成一个长的列表的空闲链表,当调用链表时,它沿着连接表寻找一个大到可以满足用户请求所需内存,将内存一分为二,将分配给用户那块内存传给用户,剩下的那块返回连接表。
十一、内存泄漏、野指针
内存泄漏:动态申请的内存空间没有被正常释放,但也不能继续被使用的情况。
野指针:指向被释放的内存或者访问受限的指针。造成的原因:1、指针未被初始化;2、被释放的指针没有被置为NULL;3、指针越界操作。
解决内存泄漏的办法:使用智能指针。
十二、static
1、局部静态变量:static局部变量和普通局部变量有什么区别?
答:static局部变量只被初始化一次,下一次依据上一次结果值;程序的局部变量存在于(堆栈)中,全局变量存在于(静态区 )中,动态申请数据存在于( 堆)中。
2、全局静态变量:static全局变量与普通的全局变量有什么区别?
答:全局变量(外部变量)的说明之前再冠以static就构成了静态的全局变量。全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。
从以上分析可以看出,把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它的使用范围。static全局变量与普通的全局变量有什么区别:static全局变量只初使化一次,防止在其他文件单元中被引用;
3、静态成员函数:static函数与普通函数有什么区别?
答:static函数与普通函数作用域不同,仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件,static函数与普通函数有什么区别:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝。
十三、union和struct
1、在存储信息时,struct可以存储多个成员,而union每个成员会共享一个存储空间,只能存储最后一个成员。
2、在任何时刻,union只存放被选中的那个成员,struct每个成员都在。
3、对union的不同成员赋值,将会对其他成员重写。
十四、new与malloc的区别
1、属性:new为关键字,malloc为库函数,需要头文件支持。
2、参数:使用new申请内存无需指定内存大小,编译器会自行计算,而malloc需要显示的给出所需内存的大小。
3、返回类型:new分配成功返回的是对象类型指针,与对象严格匹配,无需类型转换,故new是符合类型安全性操作符,malloc返回的是void*。
4、分配失败:new分配失败,抛出bad_alloc异常,malloc则是返回NULL。
5、重载。
6、内存区域:new分配的内存在自由储存区,malloc在堆上分配内存。
十五、C++类型转换
1、static_cast
2、dynamic_cast
3、const_cats
4、reinterpret_cast
#include<iostream> using namespace std; //static_cast<typeid> () 其中typeid可以为一般类型,也可以为指针引用 class A { public: A() :i(1), j(1) {} ~A() {} void printA() { count << "call printA() in class A" << endl; } void printSum() { count << "Sum = " << i + j << endl; } private: int i, j; }; class B :public A { public: B() :a(2), b(3) {} ~B() {} void printB() { count << "call printB() in class B" << endl; } void printSum() { count << "Sum = " << a + b << endl; } void Add() { a++; b++; } private: double a, b; } int main() { B *ptrB = new B; //创建一个B类型对象,堆区 ptrB->printSum(); //输出和 A *ptrA = static_cast<A*>(ptrB); //将派生类转换成父类,上行转换 ptrA->printA(); ptrA->printSum(); ptrA = new A; //创建一个父类对象 ptrB = static_cast<B*>(ptrA); //将父类对象转换成子类对象,下行转换 ptrB->printB(); ptrB->printSum(); B b; //栈上创建一个B类型对象 B &rB = b; //对b的引用 rB.printSum(); A &rA = static_cast<A &>(b); //派生类转换成基类,上行转换 rA.printA(); rA.printSum(); A a; A &rA1 = a; rA1.printA(); B &rB1 = static_cast()<B &>(a); //将基类转换成派生类,下行转换 rB1.printB(); rB1.printSum(); system("pause"); return 0; }
十六、面向对象的了解
面向对象:把数据和对数据的操作方法放在一起,做成一个相互依靠的整体,称之为对象,对同类对象抽象出共同特性,类中大多数数据只能用本类的方法进行处理。
面向对象的三大特性:封装,继承,多态。
封装:将一类事物的属性和行为抽象为一个类,使属性私有化,行为公开化,提高数据隐蔽性,复用性高。
继承:进一步将属性和行为抽象为一个父类,而每一个子类拥有父类的行为和属性,也有自己的行为和属性。
多态:接口复用。
十七、前置和后置的区别
前置++的实现比较高效,自增之后,将*this指针直接返回即可,一定要返回this指针。
后置++的实现比较麻烦,因为要返回自增之前的对象,所以先将对象进行拷贝一份,再进行自增,最后返回那个拷贝。
十八、静态库和动态库
静态库:
1、链接时将程序放进可执行的程序中。
2、可产生多个副本。
3、不依赖程序运行。
动态库:
1、程序运行时,加载时才会到动态库找函数。
2、多线程共享。
3、依赖程序运行。