声明:内容为转载 拼接 整理 而成 但是绝对的干货
一、预备知识—程序的内存分配
一个由c/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4、文字常量区—常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。
二、例子程序
这是一个前辈写的,非常详细
//main.cpp
int a = 0; //全局初始化区
int a = 0; //全局初始化区
char *p1; //全局未初始化区
main() {
int b; //栈
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "123456"; //123456\0在常量区,p3在栈上。
static int c = 0; //全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
//分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}
//p1和p2是存储在栈区的指针 但是其指向分配在堆区的那些内存空间的首地址
问题1: 在构造函数中可以使用this吗?
这个问题是不能,但是在百度的时候貌似存在一些特殊情况 好像能用,但是对于新手来说,不要在构造函数中使用。
原因是: 首先来看构造函数的作用:给创建的对象建立一个标识符; 为对象数据成员开辟内存空间; 完成对象数据成员的初始化。
只有当构造函数完成后,这个对象才是真正有效的,即this才是正确的。而再构造中使用this时,这个对象并没有完全的初始化好,所以存在问题。
问题2:在析构函数中使用delete的问题,大家看下面一段程序 。程序运行的环境的是vs2015 问题就出在析构函数中的delete
#include <iostream>
#include <windows.h>
#include <string.h>
using namespace std;
class person
{
public:
person();
person(char *nae, char sex, char *pi, int weight, int height) : name(nae), sex(sex),
weight(weight), height(height) {strcpy_s(pid,pi);};//带参数的构造函数
void change_data(char *name, char sex, char *pid, int weight, int height);//数据修改函数
void walking(int k, int v);//以 v速度行走k步
void hearing(char * sentence);//将句子中的字母小写变大写 大写变小写输出
void speaking(int n);//说出整数num的英文句子
void writeing();//在屏幕上画出汉字‘曲’
void print();//输出人的属性
void out(int a);//翻译小于1000的整数
~person();//析构函数
private:
char *name; //字符指针 本身存储在栈区 这个意思 就是要动态的申请堆区的存储空间 来存储人名 用这个指针指向申请的堆区的存储空间
char sex;
char pid[19];//身份证号码 18位 但是数组里面会有一个结束符 必须考虑哪一位的空间 定义为18个 会报错 在栈区 已经分配好了19个内存空间
int weight;
int height;
};
person::person()
{
name = new char[strlen("XXXXXX") + 1];
name = "xxxx";
//strcpy_s(name, "xxxxxx");
sex = 'M';
strcpy_s(pid,"xxxxxxxxxxxxxxxxxx");
weight = 0;
height = 0;
}
person::~person()
{
if(name !=NULL)
delete name;
}
void main()
{
person student("Tom", 'M', "372828199511071234", 100, 180);
char *a = new char[strlen("13232")];
delete a;
int A;
}
对于这个问题真是把我折磨死了要。百度了两天,终于找到了可以使我信服的解释,ps:不保证这个答案正确 但是我理解着很爽。首先看完下面这两段话:
1: 析构是个流程性概念,delete是个操作概念。就是说你可以在任意流程里执行delete操作,而析构只能在流程逻辑中体现。举个例子,你吃饭的时候可以吃苹果,但不吃饭的时候你依然可以吃苹果,吃饭就是一个流程,吃苹果就是一个操作。
2: 一般逻辑,new的对象是独立的,所以要delete,但有些是依附于特定对象b的,所以当这个对象b被析构,会将依附对象同样析构掉(一些语言编译器会建立这样的关系,或做了析构流程处理)。
这里根据第二段话的理解,这里的name就是依赖于我们声明的对象的,在程序结束的时候 代码会自动调用析构函数。这是对象被析构,那么依附这个对象的成员也就同时被析构了,内存空间也就被释放了。所以在析构函数中如果再次使用delete name;这个语句来删除内从空间就相当于删除了两次,就不对。
ps:我还有疑问: 析构函数只能释放栈内的空间,可是new 申请的空间是在堆里啊 ! 析构函数释放栈中对象的时候还能释放在堆中的对象成员的内存? 奶奶的不管了,在深究更迷糊了 ,就这样吧!有大佬看到可以给我解释一下 谢谢了。
这里还有一点需要注意:看上边第二段中后边说的那句话:(一些语言编译器会建立这样的关系,或做了析构流程处理)。但是不是所有的编译器都这样做了,我上面那个代码在vs2015中运行时崩溃的。因为我觉得vs2015就是做了这种析构流程处理。但是同样的代码我放在了 DEVC++编译器中进行运行 ,是没有问题的,说明DEVC++应该是没有做这种析构流程处理的。
下面这段小代码 是我找到了 ,可以很好的体会析构函数。大家可以自行运行一下
从输出结果可以知道:
对象是根据作用域范围而销毁的。而堆对象则需要显示调用delete关键字显示销毁。
#include <stdio.h>
class Test
{
int mi;
public:
Test(int i)
{
mi = i;
printf("Test(): %d\n", mi);
}
~Test()
{
printf("~Test(): %d\n", mi);
}
};
int main()
{
Test t(1);
Test* pt = new Test(2);
delete pt;
return 0;
}
问题3:父类指针可以直接指向子类对象,父类引用可以直接引用子类对象 这个问题的简单知识点
父子之间的赋值兼容问题:
当使用父类指针(引用)指向子类对象的时候
①子类对象退化为父类对象
②只能访问父类中定义的成员
③可以直接访问被子类覆盖的同名成员
父类子类指针函数调用注意事项
1,如果以一个基础类指针指向一个衍生类对象(派生类对象),那么经由该指针只能访问基础类定义的函数(静态联翩)
2,如果以一个衍生类指针指向一个基础类对象,必须先做强制转型动作(explicit cast),这种做法很危险,也不符合生活习惯,在程序设计上也会给程序员带来困扰。(一般不会这么去定义)
3,如果基础类和衍生类定义了相同名称的成员函数,那么通过对象指针调用成员函数时,到底调用那个函数要根据指针的原型来确定,而不是根据指针实际指向的对象类型确定。 如果有虚函数存在 那么 可以调用派生类的重写函数。
#include <iostream>
using namespace std;
class Base
{
public:
void print() { cout << 'B'; }
};
class Derived : public Base
{
public:
void print() { cout << 'D'; }
void print1() { cout << 'A'; }
};
int main()
{
Derived * pd = new Derived();
Base * pb = pd;
pb->print();
//这种语句是不对的pb->print1();
pd->print();
delete pd;
return 0;
}