C++学习笔记二(内存空间、指针、命名空间、编译步骤)

1、程序的内存空间

1.1、堆和栈

( h e a p ) (heap) (heap)和栈 ( s t a c k ) (stack) (stack)都是一种内存空间。
空间的分配是由系统自动完成的,用来存放各种变量和寄存器,无论是全局变量还是局部变量,都存储在其中。同时每个程序线程都有属于自己的栈空间,用以保证之间互不干扰。
而在一个线程当中的每一个函数在被调用的时候,也会被分配一些属于自己的栈空间,用以存放该函数所用到的局部变量和一些必要的寄存器信息。当函数被调用结束,其对应的栈的空间也会被释放。因此,局部变量的生存周期终止于栈空间被释放。
空间的分配则需要人为干涉了,它可以用来存放一些临时变量,其空间相来说也大一些。更加不同于栈的一点是,堆是任何函数都可以访问的,不存在局限性。
在这里插入图片描述

1.2、类的空间分配

1、类被实例化之前是不占内存的。
2、类的对象所占内存的大小只与成员变量有关,与成员函数无关。
3、类的成员函数和其他代码一样,被放在代码区,所有对象共享一套函数代码,但有自己的栈区存放成员变量。
4、因此类在被实例化之前之所以不占空间,不是真的不占空间,而是因为其成员函数和其他必要代码被放在代码区。所以总是能在实例化之前找到成员函数的地址的。

1.3、类的静态属性和静态方法

1.3.1、静态属性

类的属性大致分为两类,分别是静态属性和实例属性。其中实例属性又分为私有属性、公有属性、内置属性。
在这里插入图片描述
其中静态属性归类所有,不需要实例化也可以调用,放在数据段(静态存储区)。而实例属性归对象所有,放在栈区,必须实例化之后才能使用。
举个例子:

#include<iostream>
using namespace std;

class Car
{
public:
	Car(void){
		cout<<"this is a class of Car"<<endl;
	}
	~Car(void){
		cout<<"Car's class is over"<<endl;
	}
	static int temp;              //这里只是声明而已  
};

int Car::temp = 9;               //必须得在全局区域给它初始化

int main()
{
	cout<<"the number of temp is: "<<Car::temp<<endl;
	
	return 0;
}

这里有几个点需要注意:
1、静态属性和静态方法,只能放在 p u b l i c public public下。
2、定义一个全局变量和静态变量有两个步骤,第一步是声明变量,声明之后的变量会被放在 B S S ( b l o c k s t r a t e d b y s y m b o l ) BSS(block strated by symbol) BSS(blockstratedbysymbol)区域,等到第二部初始化完毕,才会被放在数据段( D a t a Data Data)中去。
3、初始化静态属性必须得在全局作用域中,而不能在 m a i n main main函数里,因为静态属性的作用域就是全局。

1.3.1、静态方法

类的方法也大致可以分为静态方法和实例方法,其中静态方法也是归类所有,不需要实例化便可以调用,而实例方法需要实例化之后才能调用。但与属性不同的是,静态方法和实例方法,都是放在文本段(代码区),用同一个内存空间。

2、指针

指针是C语言学习的时候用到,但这里再次记录一下,以便理解后面的内容。

2.1、指针的本质

指针是一种存放地址的特殊变量,它也有属于自己的地址。因此,指针并不等于地址!!!
我们可以通过以下代码查看指针存放的地址,以及指针本身的地址。

void main()
{
	int *point;
	
	printf("point's value is %p\n",point);
	printf("point's address is %p\n",&point);
}

再通过以下几个例子可以进一步了解指针本质。

错误示例:
	int *p;  
	*p = 9;

错误原因:这里 p p p指针存放的是 N U L L NULL NULL的地址,所以这句代码相当于让 N U L L NULL NULL地址对应的变量 ( N U L L ) (NULL) (NULL)去存放9这个数。而 N U L L NULL NULL即是空,让空去存放东西,显然是错误的。

正确示例1:
	int *p;
	int a;
	p = &a;
	a = 9;
正确示例2:
	int *p;
	int a;
	p = &a;
	*p = 9;

正确原因:通过 p = p= p=& a a a;这句代码让p指针存放a变量的地址,那么无论是直接修改a,还是通过地址索引a再修改之,结果都是一样的。
此外,由于在32为计算机,地址则占4个字节,在64位计算机中,地址占6个字节或8个字节的缘故,指针的大小也是其对应地址所占的空间。

2.2、指针的意义

指针可以说是C语言的灵魂,即便在C++当中也发挥着巨大的作用。以下对于指针的意义仅是我个人学习过程中的所感所获。
意义一:在大部分程序语言中,函数的返回值一般分为两种情况,一种是返回一个值,另一种则是返回一个地址。我们称之为传值传址。而传址的目的,还是为了获得目标值,所以此时用到的就是指针的解引用功能。
当然,一般不再用return,也是在函数内直接调用另外一个目标函数,然后把地址作为参数传进去。如下例子。这样做的原因是为了避免在return之后,局部变量的生命中止,原地址被释放。
意义二:指针的存在,相当于提供了一个访问变量的新通道,通过指针,我们可以跨栈区去范围变量,当然也可以访问堆。下面讲malloc和new的时候会用到。

#include<stdio.h>

void function_1(void);
void function_2(int *);


void function_1()
{
	int i,temp[10];
	
	for(i=0;i<10;i++)
	{
		temp[i] = i;
	}
	
	function_2(temp);
	
}

void function_2(int *temp)
{
	printf("the value is %d \n",temp[5]);
}

int main()
{
	function_1();
	
	return 0;
}

2.3、清空指针

再次强调,指针其实一种存放地址的特殊变量,所以清空指针,是让地址不指向任何值的意思。那么让其指向 N U L L NULL NULL即可。

*point = NULL;

2.4、常量指针和指针常量

2.4.1、常量指针(const int *p;)

名字记忆: c o n s t ( c o n s t a n t : const(constant: const(constant常量) + i n t ∗ p ( p o i n t : + int *p(point: +intp(point指针) = 常量指针。
用法记忆: c o n s t const const后面的东西不能被修改 − − > ∗ p -->*p >p不能被修改 − − > --> >不能通过解引用来改变指针指向的值。
即指针所指向的地址可以变,指针所指向的值也可以改变。但不过通过解引用指针来改变指针指向的值。

eg_1:(正常使用)
int temp = 9;
const int *p;
p = &temp;
printf("%d",*p);  //输出结果是9

eg_2:(改变变量的值,进而改变指针指向的值)
int temp = 9;
const int *p = &temp;
temp = 3;
printf("%d",*p);  //输出结果是3

eg_3:(改变指针存放的地址)
int temp1=3,temp2=5;
const int *p = &temp1;
p = &temp2;
printf("%d",*p);  //输出结果是5

eg_4:(通过解引用改变指针指向的值❌)
int temp = 9;
const int *p;
p = &temp;
*p = 3;    //不能通过解引用指针来改变指针所指向的值
printf("%d",*p);  //程序报错❌

2.4.2、指针常量(int *const p;)

名字记忆: i n t ∗ p ( p o i n t int *p(point intp(point:指针) + c o n s t ( c o n s t a n t : + const(constant: +const(constant常量) = 指针常量。
用法记忆:const后面的东西不能被修改---->p不能被修改---->p是一个地址---->地址不能被修改。
即可以改变指针指向的值,也可以通过解引用指针来改变指针指向的值,但就是不能修改指针存放的地址。

eg_1:(正常使用)
int temp = 9;
const int *p;
p = &temp;
printf("%d",*p);  //输出结果是9

eg_2:(通过解引用改变指针指向的值)
int temp = 9;
const int *p;
*p = 7;
printf("%d",*p);  //输出结果是7

eg_3:(改变变量的值,进而改变指针指向的值)
int temp = 7;
const int *p = &temp;
temp = 9;
printf("%d",*p);  //输出结果是9

eg_4:(改变指针存放的地址❌)
int temp1=3,temp2=5;
const int *p = &temp1;
p = &temp2;             //不能改变指针存放的地址
printf("%d",*p);        //程序报错❌

最后提一句,const来头不简单,const修饰的数据是被丢到只读存储区 ( r e a d o n l y m e m o r y ) (read only memory) (readonlymemory)的。从内存上看, i n t int int c o n s t i n t const int constint存放在完全不同的地方。

2.5、C++类中的this

t h i s this this是每一个对象(类的实例化)都有的一个隐含参数,它是对象本身的地址,作用域也在仅限于对象自身。
注:声明一个东西,无论是类还是其他变量,它本身的变量不占空间的,只有实例化之后才开始占据内存,才有所谓的地址。
举个例子:

class Car
{
public:
	int temp1;
	Car(int num){
		cout<<"the license plate of this car is:"<<num<<endl; 
	} 
	void run(){
		int num = this->temp1;    //访问对象的temp1属性,当然在这里不加这个this也是可以的。用->是因为this是指针。
	}
private:
	int temp2;
};

3、malloc and new

3.1、malloc和new的异同

m a l l o c malloc malloc n e w new new都是用来向堆申请内存空间的函数指令,其返回值都是一个地址。
其中malloc的函数定义是: v o i d ∗ m a l l o c ( u n s i g n e d i n t s i z e ) void * malloc(unsigned int size) voidmalloc(unsignedintsize); 可以看出, m a l l o c malloc malloc为程序申请了一个 s i z e size size字节大小的控制,并返回一个这个空间的首地址。

使用方法:
int *p = malloc(4);
float *p = malloc(4);
...
free(p); //malloc和free是成对出现的,使用完内存,要用free把内存还回去。

new则常用于 C + + C++ C++为类分配内存空间,可以理解为malloc的进化版。new的实现过程可以分为两步,第一步和 m a l l o c malloc malloc一样,向堆申请空间。第二步则是执行构造器,对类进行初始化。

#include <iostream>

using namespace std;


class Car
{
public:
	Car(int num){
		cout<<"the license plate of this car is:"<<num<<endl; 
	} 
	int temp1;
private:
	int temp2;         
};


int main()
{
	Car *mycar = new Car(888);
	mycar->temp1 = 5;	
	//mycar->temp2 = 5;  ×,即便指针,也不能访问私有变量  
	delete(mycar);     //delate和new成对出现,也是为了释放已经使用完毕的堆空间,反正内存泄漏。
	mycar = NULL; //最好还是清空一下指针
	
	return 0;
}

其中, C a r ∗ m y c a r = n e w C a r ( 888 ) Car *mycar = new Car(888) Carmycar=newCar(888)意思是向堆申请一块可以存放这个类所有成员函数和变量的空间,返回这个空间的首地址。那么当然得用指向这个类的指针去接收这个地址了。而由于mycar此时已经是一个指针,那么要去访问类里面的成员变量就要用到’->‘而不是’.'了。

mycar->temp1 等于 (*mycar).temp1

3.2、new的两种用法

C + + C++ C++的编程过程中,会产生很多父类以及它们对应的子类。所以用 n e w new new来分配空间的时候也产生了两种选择。

1、父类名 *p1 = new 子类名;    
2、子类名 *p2 = new 子类名;

注:子类名 *p = new 父类名;    是错误的❌

在第一种情况中,可以表述为,向堆申请子类的空间,用的却是父类的指针。 p 1 p1 p1可以访问父类 p u b l i c public public中的任何属性和方法,但不可以访问子类独有的属性和方法。另外,当父类的虚方法在子类中有重载的时候,则以子类为主,否则以父类为主。
在第二种情况中,可以表述为,向堆申请子类的空间,且用的是子类的指针。故 p 2 p2 p2不仅可以访问父类 p u b l i c public public中的任何属性和方法,也可以访问子类独有的属性和方法。

#include<iostream>
using namespace std;

class Superclass
{
public:
	void function_1(void){
		cout<<"the father's function 1"<<endl;
	}
	void function_2(void){
		cout<<"the father's function 2"<<endl;
	}
	virtual void function_3(void){
		cout<<"the father's function 3"<<endl;
	}
private:
	void function_4(void){
		cout<<"the father's function 4"<<endl;
	}
};

class Subclass:public Superclass
{
public:
	void function_1(void){
		cout<<"the son's function 1"<<endl;
	}
	void function_3(void){
		cout<<"the son's function 3"<<endl;
	}
	void function_5(void){
		cout<<"the son's function 5"<<endl;
	}

};

int main()
{
	Superclass *p1 = new Subclass;
	Subclass *p2 = new Subclass;
	
	p1->function_1();		//print "the father's function 1"
	p1->function_2();		//print "the father's function 2"		
	p1->function_3();		//print "the son's function 3"
//	p1->function_4();		//wrong, the function doesn't exist in the subclass space❌
//	p1->function_5();		//wrong, the pointor of the superclass can't access method unique to the subclass❌

	p2->function_1();		//print "the son's function 1"
//	p2->function_2();		//wrong,there is not function_2 in the subclass❌
	p2->function_3();		//print "the son's function 3"
//	p2->function_4(); 		//wrong, subclass can't access the private function of superclass❌
	p2->function_5(); 		//print "the son's function 5"
}

4、命名空间

4.1、创建命名空间

一般把命名空间放在头文件里,不同的命名空间也是使用不同的栈区,这使得它们即使有相同名字的变量也毫不冲突。
在以下的这个例子里,假设我们创建了一个myspace.h文件,然把命名空间到定义放在.h文件中。

#ifndef MY_SPACE     // ifndef == if no define。防止多次引用头文件的时候,重复执行以下代码
#define MY_SPACE   

namespace  myspace
{
	class test_1;                   //可以在花括号外再定义类,在这里只是声明一下test_1属于myspace命名空间里的类。
	void test_2(void);
	
	void test_2(void)        //函数必须在花括号内定义好
	{
		cout<<"test..."<<endl;
	}
}

class test_1
{
public:
		test_1(void){
			cout<<"i am the construct of the test_1"<<endl;
		}
		void test_method(void);
};

#endif

4.2、使用命名空间

#include "myspace.h"  //引用自己创建的头文件用“”号,引用系统的文件的时候用<>号。
using namespace myspace;

用了以上这句代码,就可以在该源文件中使用myspace里的所有函数和类。这其实和之前用到的using namespace std是一样的原理。但如果怕不同命名空间在这个文件中出现同名函数或同名变量冲突,可以不使用using namespace …,在需要用到该空间函数或变量的时候再使用以下语句。

myspace::函数or变量

5、编译程序的四个步骤

5.1、预处理(Pre-process)

预处理阶段会将所有必要的头文件拉取进来,并展开程序中的宏定义(# d e f i n e define define)、判别代码的存留(# i f d e f ifdef ifdef,# e n d i f . . . endif... endif...)、删除代码中的注释等等,最后留下需要执行的代码内容,生成 . i .i .i文件。

5.2、编译(Compile)

检查程序的语法和规范等问题,并把代码转化成汇编语言。同时在这个步骤,编译器会为程序分配好内存空间,并处理好常量(const),最后生成 . s .s .s文件。

5.3、汇编(Assembly)

. s .s .s文件中的汇编码转化为机器能读懂的二进制指令,生成 . o .o .o文件(也称二进制文件)。

5.4、链接(Link)

将多个二进制文件链接起来,形成一个可执行文件。比如在Windows系统下的可执行文件是 . e x e .exe .exe作为后缀的。
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

__TAT__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值