int *p;
p = (int*)malloc(sizeof(int) * 128);
//分配128个(可根据实际需要替换该数值)整型存储单元,
//并将这128个连续的整型存储单元的首地址存储到指针变量p中
int *parr;
parr = new int[100](0);
//返回类型为int *类型(整数型指针),分配大小为sizeof(int) * 100;
//初始化为0
共同点是:
都是从堆上申请空间,并且需要用户手动释放。
不同的地方是:
C++:
C语言:
静态区域:
动态区域:
如何避免内存泄漏
内存泄漏:动态分配的堆内存本应释放而未释放,造成可用空间减少。
堆 | 栈 | |
---|---|---|
管理方式 | new,delete,手动 | 编译器自动管理 |
空间大小(32位) | 4G | 1M |
产生碎片 | 产生大量碎片 | 无碎片 |
生成方式 | 低地址–>高地址 | 高地址–>低地址 |
分配方式 | 动态分配 | 静态,动态(alloca,自动释放) |
分配效率 | 低 | 高 |
智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。C++ 11中最常用的智能指针类型为shared_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。该引用计数的内存在堆上分配。当新增一个时引用计数加1,当过期时引用计数减一。只有引用计数为0时,智能指针才会自动释放引用的内存资源。对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类。可以通过make_shared函数或者通过构造函数传入普通指针。并可以通过get函数获得普通指针。
c++通过public、protect、private三个关键字来控制成员变量和成员函数的访问权限,即公有的、受保护的、私有的, 被称为成员访问限定符 。在类的内部,可以相互访问没有限制。在类的外部,publiic可以通过对象、友元函数、子类函数访问,protect可以通过友元函数、子类函数访问,private只能通过友元函数访问。
public:
private:
protected:
全局静态变量:在全局变量前加一个static关键字
局部静态变量:在局部变量前加一个static关键字
静态函数:在函数前加一个static关键字
静态数据成员:在类内数据成员前加一个static关键字
静态成员函数:
属于类的静态成员
不具有this指针,静态成员函数无法访问非静态成员,只能调用静态成员; 非静态成员函数可以任意地访问静态成员;
可以通过类名和对象名进行访问
C语言设计首先考虑是如何通过一个过程,对输入进行处理得到结果。
C++首先考虑如何构造一个对象模型,使用对象解决问题。
面向过程是一种以过程为中心的编程思想,以算法进行驱动,程序=算法➕数据
面向对象是一种以对象为中心的编程思想,以消息进行驱动,程序=对象➕消息
C++中的struct可以创建成员函数,而C中的struct不可创建成员函数
C++中struct可以有继承,虚函数,多态,而C中struct不可以。
C++中class中默认访问类型是private,而struct默认是public。
C++中class可以定义模板参数,但struct不可以。
struct可用来定义简单数据结构,class用来定义复杂对象
不是,如全局变量、静态变量的初始化,异常中断、内联、宏定义等
引入c库文件include"a.h" 或者 extern"c"{ 函数名}
c++支持函数重载,在编译生成的汇编码中,要对函数的名字进行一些处理,比如加入函数返回类型等等
而c不支持函数重载,只是简单的函数名
函数被c++编译后在库中的名字与c语言不同,所以c++程序无法直接调用c函数
宏不是函数,只是在编译前将程序中相关字符串替换成宏体;内联函数是函数,在编译时不产生单独的代码,而是将相关代码嵌入到调用处,减少类普通函数调用时的资源消耗
宏没有返回值,不需要做参数类型检查;内联函数有返回值,需要做类型参数检查,比较安全
在类中声明同时定义的成员函数自动转化为内联函数(inline必须连着函数体)
内联函数适用范围
虚函数可以是内联函数,但是当虚函数表现多态性的时候不能内联
因为内联是编译的时候确定的,而多态是在运行时,编译器无法知道运行期间调用哪个代码
定义:指针指向一块随机的空间,不受程序控制
原因:
规避方法:
构造函数是在创建对象的时候进行调用,主要用来对对象成员变量进行初始化赋初值。一个对象可以有多个构造函数,可以重载构造函数(根据参数列表的不同)。
析构函数是在对象生命周期结束时释放对象所占用的资源。析构函数是特殊的类成员函数,他的名字与类名相同,没有返回值,没有参数不可以随意调用与重载,只由系统自动调用。
一个类的成员变量的初始化时机取决于构造函数的实现,如果是在构造函数内部实现,那先调用构造函数,再初始化成员变量。如果是在成员初始化列表里初始化,那就是先初始化类成员,然后调用构造函数。
拷贝构造函数是一特殊的构造函数,函数名称必须和类名一致,他的必须一个参数是本类型的一个引用变量。 X(const X& A){value = A.value;}
一个类中可以出现多于一个拷贝构造函数。
对于一个类X, 如果一个构造函数的第一个参数是下列之一:
a) X&
b) const X&
c) volatile X&
d) const volatile X&
且没有其他参数或其他参数都有默认值,那么这个函数是拷贝构造函数;
使用引用是因为:避免拷贝构造函数无限制的递归下去;
调用时机:
深拷贝和浅拷贝
浅拷贝:默认的拷贝构造函数不会对对象中静态成员进行操作,对于需要动态开辟空间的指针只是做了简单的赋值,将指针指向了同一块内存,当析构时就对同一个空间释放两次,出现错误。
深拷贝:就是对对象中的动态成员重新分配空间,然后进行内容的赋值。
1.使用引用参数的主要原因?
程序员能够修改调用函数中的数据对象
通过传递引用而不是整个数据对象,可以提高运行速度
2.对于使用传递的值而不作修改的函数:
如果数据对象很小,如内置数据对象,则按值传递
如果数据对象是数组,则使用指针,并将指针声明为指向const的指针
如果数据对象是较大的结构则使用const指针或const引用,以提高效率,节省复制结构所需要的时间和空间
如果数据对象是类对象则使用const引用。
3.对于修改调用函数中数据的函数:
如果数据对象是内置数据类型则使用指针
如果数据对象是数组则只能使用指针
如果数据对象是结构则使用引用或者指针
如果数据对象是类对象则使用引用
长度 | 32位系统 | 64位系统 |
---|---|---|
char | 1 | 1 |
char* | 4 | 8 |
short | 2 | 2 |
int | 4 | 4 |
float | 4 | 4 |
long | 4 | 8 |
long long | 8 | 8 |
double | 8 | 8 |
#include
#include
srand(time(0)); //产生随机数种子,每次运行为不同结果,不加每次运行为同一结果
rang()%(b-a+1)+a;// [a,b]中的随机整数
//char* s="alsd"的内容无法修改 char s[10]="aklsjd"的内容可以修改
//string --> const char* .c_str() .data()
string s="abcde";
const char *k = s.c_str();
const char *t = s.data()
//string --> char* .copy() 通过const char*转移
char* =(const char*) //const char* 指针常量
string s="abcde";
int len = s.size();
char *a=new char[len+1]; //在堆上申请一个len+1大小的空间 因为还有'\0'结束符
//char *a = (char*)malloc((len+1)*sizeof(char));
s.copy(s,len,0);
//char* --> string 直接赋值
char *p = "asdhl";
string s = p;
//char* --> char[] strcpy()
char *p = "asdas";
char a[100];
strcpy(a,p);
//char[] -->char* 直接赋值
char a[]="akjsd";
char *p=a;
//char[] -->string 直接赋值
char p[]="asld";
string s=p;
//string --> char[] 数组赋值
int len =s.size();
for(int i=0;i
总结:
sizeof是运算符,strlen是函数。 数组做sizeof的参数不退化,传递给strlen就退化为指针了 。
//sizeof求的是字符串在内存的大小(包括'\0'),strlen求的是字符串到'\0'位置的大小(不包括'\0')
sizeof("abc") = 4;
strlen("abc") = 3;
char *a="qwerqwe";
sizeof(a) = 4; //指针大小
sizeof(*a) = 1; //*a指向'q',char的大小
strlen(a) = 7; //字符串长度
char b[10]="qwerqwe";
sizeof(b) = 10; //数组大小
sizeof(*b) = 1; //*b指向'q',char的大小
strlen(b) = 7; //字符串长度
size_t strlen(const char *str){
size_t len =0;
assert(str!=NULL);
while(*str++!='\0') len++;
return len;
}
int a[4]={1,2,3,4};
int *p = (int *)(&a+1);
cout<<*(a+1)<<*(p-1)<
从右往左入栈。栈底高地址,栈顶低地址,最先入栈的是函数调用处下条指令的地址,这样调用完成后返回到该地址继续执行,然后才是函数的入参。调用结束将栈内容出栈,最后栈顶指向开始指向了开始存储的指令地址。
int a=2;
cout<
重写 override
定义:子类重写基类的虚函数
特点:(1)函数名相同 (2) 作用域不同(3) 参数列表相同(4)返回值相同
重载 overload
定义:在同一个作用域下根据参数来选择执行函数
特点:(1)函数名相同 (2)作用域相同 (3) 参数列表不同(4)返回值可以不同
重定义 overwrite
定义:子类重定义基类的虚函数
特点:(1)函数名相同 (2)作用域不同 (3)参数列表可以不同 (4) 返回值可以不同
**参数列表不同**:指的是个数或类型,但是不能靠返回类型来判断
是对于不同对象接收相同消息时产生不同的动作。C++的多态性具体体现在运行和编译两个方面:在程序运行时的多态性通过继承和虚函数来体现;在程序编译时多态性体现在函数和运算符的重载上;
动态绑定的条件是基类中的函数必须是虚函数,且派生类一定要重写基类的虚函数;第二点是通过基类类型的引用或者指针调用虚函数。
在基类函数前加上virtual,在派生类里面重写该函数,运行的时候根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。
派生类指针不可以指向基类对象
https://blog.csdn.net/qq_39755395/article/details/79751362
哪些函数不可定义为虚函数?
https://blog.csdn.net/ZongYinHu/article/details/51276806?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
构造函数和析构函数中调用虚函数不会产生多态
单继承:
每个类都有属于自己的虚函数表,派生类之间互不影响
类对象只存储指向虚函数表的指针vfptr,一个类的虚函数表只有一份,为所有对象共享
多继承:
派生类有几个基类就有该类几张虚函数表,派生类的虚函数表在最先声明的基类虚函数表中
如:C继承A,B,会有两个虚函数表指针,每个指针下先存放基类的成员变量
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RzTYkSik-1591780187357)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1584189181398.png)]
1.pa与pc指向对象c的首地址,但pa和pb相差4字节
2.pa只能调用A中有声明的函数(func()被C覆盖),pb调用B中有声明的函数(func()被C覆盖),pc都可以调用
3.由于多态,访问C的func()
4.通过加作用域:pa->A::func();
在虚函数表中,子类覆盖了父类的虚函数表,可以从虚函数调用函数,也可以直接::限定作用域直接从A类的代码区寻找函数。
如果将pb强制转化成pa,pa->funcA()会根据偏移量自动回到pb->funcB();
5.在pa->funcC()无法调用,因为父类并没有这个函数,属于子类的成员函数,可以将其强制转换成C类型指针调用。
二义性也可以使用作用域限定符(d.Base::fun())
区别:虚基类只维护一份成员副本,而后者在派生类中拥有同名成员的多个副本,可以存放不同的数据。前者更加简洁,节省空间,后者可以容乃更多的数据。
在第一级继承时就要将共同基类设计为虚基类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-he35veQF-1591780187360)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1589090750810.png)]
虚函数是根据一张虚函数表,每创建一个类对象, 根据对象实例的地址得到这张虚函数表 ,遍历得到其中的函数。
虚函数是为了让派生类可以重写这个函数,若派生类没有重写就是用基类的函数。
纯虚函数声明如下: virtual void Fun()=0; 纯虚函数一定没有定义,纯虚函数用来规范派生类的行为,即接口。包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的指针或引用。
为什么默认的析构函数不是虚函数?什么情况下析构函数应该声明为虚函数?
因为当类不作为基类时,如果将析构函数设置为虚函数,编译器就会给类增添一个虚函数表,里面面存放虚函数指针,会增加类的存储空间。
当基类的的指针指向派生类,并用基类的指针删除派生类对象时,当delete时,基类的指针指向派生类的析构函数,而派生类的析构函数又会调用基类的析构函数,这样整个派生类的对象完全被释放掉了。如果析构函数不是虚函数的话,编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数,这样就会造成派生类对象析构不完全。
<>表示编译器会从系统配置的库环境中去寻找
""表示编译器先从当前项目的当前目录文件夹中寻找,找不到再去系统配置的库环境中去寻找
所以自己定义头文件需要使用"";
是现在c++的规定,将标准c++库的组件放在一个名叫std的namespace里面
预处理—->编译—->汇编—->链接
预处理:编译器处理C程序的头文件,还有宏的替换等命令
编译:这个阶段编译器主要做词法分析、语法分析、语义分析等,在检查无错误后后,把代码翻译成汇编语言
汇编:汇编语言变成机器语言
链接:将编译阶段生成的文件以及他们所需要的库函数链接为一个可执行文件
https://blog.csdn.net/kang___xi/article/details/80210717
静态链接和动态链接两者最大的区别就在于链接的时机不一样,静态链接是在形成可执行程序前,而动态链接的进行则是在程序执行时。
静态链接:把要调用的函数或者过程直接链接到可执行文件中,成为可执行文件的一部分。换句话说,就是函数代码会在exe文件中,这个文件包括类运行所需要的全部代码。
缺点:(1)当多个程序调用相同函数时,每一个程序都会复制一次函数代码,浪费内存资源
(2)函数更新就需要重新进行编译链接可执行文件。
动态链接:仅仅在文件中加入了所调用函数的描述信息(重定位信息)。当应用程序被装进内存开始运行时,操作系统才将应用程序与动态链接库连接起来。当第一个程序程序使用了函数,第二个函数使用该函数不需要重新加载。
**默认值设置:**可以在函数声明或函数定义中设置
**默认值位置:**指定默认值的参数必须位于形参的最右端,从右往左。第一个默认值后面的所有形参都必须指定默认值
**默认值的类型:**默认值可以是常量、全局变量里或者全局函数,不可以局部变量。因为函数参数默认值是在编译的时候确定的,而局部变量存放在栈中,编译时不能确定。
**参数是引用时的默认值:**全局变量可以作为引用参数默认值,不可以是实际值,因为引用必须引用一个对象
11223344 | 低地址 | 高地址 | |||
---|---|---|---|---|---|
高尾端(大端) | 11 | 22 | 33 | 44 | |
低尾段(小端) | 44 | 33 | 22 | 11 |
bool IsBigEndian(){
union NUM{
int a;
char b[4];
}num;
num.a = 0x11223344;
if( num.b[3] == 0x11 ){
return true;
}
return false;
}