C++学习笔记(超详细笔记记录ing)

C++学习笔记(8)

学习是一件任重而道远的事情,与其焦虑不如动手起来,借助平台记录自己学习笔记,希望和大家多多交流,今天又是努力成为程序媛的一天!

14.程序的内存模型

本阶段主要针对C++面向对象编程技术做详细讲解

  • 内存分区模型(C++程序执行时,将内存大方向划分为4个区域)
    - 代码区:存放函数的二进制代码,由操作系统进行管理
    - 全局区:存放全局变量和静态变量以及常量
    - 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
    - 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收

意义:不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程

程序编译后,生成exe可执行程序,未执行程序前分为两个区域:代码区和全局区 -

14.1代码区

存放CPU执行的机器指令
代码是共享的:目的是对于频繁执行的程序,只需要在内存中有一份代码即可
代码是只读的:防止程序意外修改它的指令

14.2全局区

全局变量和静态变量存放在此
还包含常量区,字符常量和其他常量也在
该区域的数据在程序结束后由操作系统释放

#include<iostream>
using namespace std;
#include<string>

//创建一个全局变量//只要不在函数体里的变量就是全局变量
int g_a = 20;
int g_b = 20;

//const修饰的全局常量
const int c_g_a = 10;
const int c_g_b = 10;

int main() {

	//全局区
	//全局变量 静态变量 常量



	//创建一个非全局区的普通局部变量
	int a = 10;
	int b = 10;

	cout << "非全局区的局部变量a的地址为:" << (long long)&a << endl;
	cout << "非全局区的局部变量b的地址为:" << (long long)&b << endl;

	cout << "全局变量g_a的地址为:" << (long long)&g_a << endl;
	cout << "全局变量g_b的地址为:" << (long long)&g_b << endl;

	//静态变量  在普通变量前加static,属于静态变量
	static int s_a = 10;
	static int s_b = 10;
	cout << "静态变量s_a的地址为" << (long long)&s_a << endl;
	cout << "静态变量s_b的地址为" << (long long)&s_b << endl;


	//常量
	//字符串常量
	cout << "字符串常量的地址为:" << (long long)&"hello world" << endl;

	//const修饰的变量
	//const修改的全局变量
	cout << "全局常量 c_g_a的地址为:" << (long long)&c_g_a << endl;
	cout << "全局常量 c_g_b的地址为:" << (long long)&c_g_b << endl;

	//const修饰的局部变量
	const int c_l_a = 10;
	const int c_l_b = 10;
	cout << "const修饰的局部变量 c_l_a的地址为:" << (long long)&c_l_a << endl;
	cout << "const修饰的局部变量 c_l_b的地址为:" << (long long)&c_l_b << endl;

	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述

注意:

  • 只要不在函数体里定义的变量都是全局变量
  • 变量名,c-const;g-global;l-local;const修饰的变量,全局变量,局部变量
  • 静态变量即在普通变量前面加static,属于静态变量
  • 常量分为以下几种:
  • 1.字符串常量 2.const修饰的全局变量 3.const修饰的局部变量
  • 根据地址可见,局部变量和全局区地址很远,不在一个区
  • 全局区内有:全局变量;静态变量(static);字符串变量。const修饰的全局变量
  • 局部变量和const修饰的局部变量不在全局区

14.3栈区

由编译器自动分配释放,存放函数的参数值,局部变量等
注意:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

#include<iostream>
using namespace std;

//栈区注意事项  -- 不要返回局部变量地址

//栈区的数据由编译器管理开辟和释放


int * func() {//用int指针数据类型来接收地址
	int a = 10;//局部变量,存放在栈区,栈区的数据在函数执行完后自动释放
	int b = 20;
	string name = "abc";//就定一个变量时,地址怎么返回都不变,但是函数稍微复杂点,变量多时就不是正确数字了
	char s = 'q';
	return &a;//返回局部变量的地址
}

int main() {

	int * p = func();//接收函数的返回值//运行完这个函数后内存就被释放了

	cout << *p << endl;//第一次可以打印正确数字,因为编译器做了保留
	cout << *p << endl;//第二次数据就不再保留了
	cout << *p << endl;
	cout << *p << endl;
	

	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述
栈区存放形参(形参开辟的数据)和局部变量,不要返回局部变量的地址,因为局部变量在函数执行完之后就自动释放了,再用指针去操作那个地址时,就是非法操作了。

理解:
几点注意:

  1. 关于栈区:当一个函数运行结束后,这个函数在栈区所有变量都将被删除,这些变量占用的内存空间也都将被释放,相当于清空和销毁变量
  2. 在这里,栈区的变量是如何变化的:
    首先在栈区创建了一个局部变量,给a一个内存地址存放的值为10,用指针接收返回a的内存地址,然而当主程序运行func()函数时,函数一执行完,那些变量和地址空间都被销毁了,不过当时a的地址返回给了主程序的指针p,但此时p指向的原地址早就没有没有a的值了,所以出来的乱码值,每次调用该函数如此,第一次之所以可以打印正确数字因为编译器做了保留。

14.4堆区

由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
在C++中主要利用new在堆区开辟内存


#include<iostream>
using namespace std;

int*  func() {
	//指针本质是局部变量 放在栈上  指针保存的数据是放在堆区,即堆区创建的变量地址,它是不变的
	int* p = new int(10);//利用new关键字 将数据开辟到堆区
	int b = 20; 
	string name = "abs";
	return p;
}



int main() {
	//在堆区开辟数据
	int* p1 = func();

	cout << (long long)p1 << endl;
	cout << (long long)p1 << endl;
	cout << (long long)p1 << endl;
	cout << *p1 << endl;

	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述
理解:
几点注意:

  1. 堆区创建的变量由程序员管理释放的,这其中的变量可以在程序任何地方访问
  2. 堆区内存访问需要用到指针
  3. 通常用new()或malloc()在堆区开辟数据,创建之后是返回的地址,所以需要指针来接收它
  4. 堆区的变量占用的内存地址和值除程序员不会自动改动,即一般是固定的地址
  5. 在这里,堆区变量是这样变化的:
    首先在堆区开辟了一个int的地址空间,存放数值为10,这是不随程序运行改变的,是固定的,然后在栈区创建了一个指针变量的地址,指针变量接收的值就是10对应的那个int地址号,函数最后返回int地址。到主程序时,先是运行函数,完成上述过程,然后返回int地址给指针p1,随着函数运行结束,栈区的变量全部销毁,而p1依旧代表int那个地址号,而用指针去访问那个地址时,就是int地址存放的数据10;而p1始终指向的都是int 那个地址。无论运行多次,结果都是10以及其存放的地址。即栈区变量虽然没了,但是堆区地址和值不变。

14.5new操作符

C++中利用new操作符在堆区开辟数据
堆区开辟的数据,由程序员手动开辟,利用操作符delete释放
创建语法:new 数据类型
利用new创建的数据,会返回该数据对应的类型的指针

#include<iostream>
using namespace std;
#include<string>


//1.new的基本用法
int* func() {
	
	int* p = new int(10);//利用new关键字 将数据开辟到堆区
	
	return p;//new返回的是 该数据类型的指针
}


void test01() {
	int* p = func();
	cout << *p << endl;
	cout << *p << endl;

	//如果想释放堆区的数据,利用关键字delete
	delete p;

	cout << *p << endl;//内存已经被释放,再次访问就是非法操作,会报错
}


void test02() {
	//2.在堆区利用new创建数组

	int* arr = new int[10];//代表创建10个元素的数组

	for (int i = 0; i < 10; i++) {
		arr[i] = i;//创建数组值0-9
		cout << arr[i] << endl;
	}

	//如果要删除堆区创建的数组 要加[]才可!!!
	delete[] arr;
}

int main() {
	

	//test01();
	test02();

	system("pause");
	return 0;
}

test01运行结果:
在这里插入图片描述
test02:
在这里插入图片描述

  • 如果要删除堆区创建的数组 要加[]才可!!!

15.C++中的引用

15.1 引用的基本使用

作用:给变量起别名
语法:数据类型 &别名 = 原名

#include<iostream>
using namespace std;
#include<string>



int main() {


	//引用基本语法
	//数据类型  &别名 = 原名

	int a = 10;
	//创建引用
	//就是给变量起别名
	//操作原名和别名就是操作同一个变量
	int& b = a;

	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	b = 100;

	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述

15.2 引用的注意事项

  • 引用必须初始化
  • 引用初始化后,不可以改变
#include<iostream>
using namespace std;
#include<string>

int main() {
	int a = 10;
	int& b = a;//初始化
	//1.引用必须初始化
	//int& b;//错误,必须要初始化


	//2.引用在初始化后,不可以改变
	int c = 20;

	b = c;//这不是在更改引用,而是赋值操作

	cout << "a = " << a << endl;//20
	cout << "b = " << b << endl;//20
	cout << "c = " << c << endl;//20

	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述

  • 这里b已经初始化了作为a的别名,后续c相当于赋值给b,而不是更改引用
  • 所以c的值赋给了b为20,而b引用a,a也为20

15.3 引用做函数参数

作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参

#include<iostream>
using namespace std;
#include<string>


//交换函数

//1.值传递
void myswap01(int a,int b) {
	int temp = a;
	a = b;
	b = temp;
	cout << "myswap01()中a = " << a << endl;
	cout << "myswap01()中b = " << b << endl;
}

//2.地址传递

void myswap02(int* a, int* b) {
	int temp = *a;
	*a = *b;
	*b = temp;
	cout << "myswap02()中a = " << *a << endl;
	cout << "myswap02()中b = " << *b << endl;
}

//3.引用传递
void myswap03(int& a, int& b) {
	int temp = a;
	a = b;
	b = temp;
	cout << "myswap03()中a = " << a << endl;
	cout << "myswap03()中b = " << b << endl;

}

int main() {

	int a = 10;
	int b = 20;
	myswap01(a, b);//值传递,形参不会修饰实参
	//myswap02(&a, &b);//地址传递,形参会修饰实参
	//myswap03(a, b);//引用传递,形参会修饰实参

	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	

	system("pause");
	return 0;
}

myswap01(a, b)函数交换结果:
在这里插入图片描述
myswap02(&a, &b)函数交换结果:
在这里插入图片描述
myswap03(a, b)函数交换结果:
在这里插入图片描述
引用传递里的形参相当于引用实参a,b修改形参就相当于改变了实参a,b的值

15.4 引用做函数返回值

作用:引用是可以作为函数的返回值存在的

注意:不要返回局部变量引用
因为局部变量在栈区,一旦调用函数结束后,栈区定义的变量就销毁了,主程序引用的即其变量的别名也不在了,即非法访问,没有意义
用法:函数调用作为左值
函数调用返回的变量值作为左值,可以给其赋值,不过记住栈区里变量是无法访问的,销毁后无法访问,非法操作,赋值给函数调用后,相当于改了变量值和别名

#include<iostream>
using namespace std;
#include<string>

//引用做函数的返回值


//1.不要返回局部变量的引用
int &test01() {//用引用方式做一个返回
	int a = 10;//局部变量存放在栈区  
	return a;
}



//2.函数的调用可以作为左值
int& test02() {
	static int a = 10;//静态变量在全局区,在程序结束后系统释放
	return a;
}
int main() {

	int &ref = test01();

	cout << "ref = " << ref << endl;//收到的返回值a引用给到ref,而此时a已经销毁了,所以非法操作,乱码值
	
	int& ref2 = test02();
	cout << "ref2 = " << ref2 << endl;
	cout << "ref2 = " << ref2 << endl;//静态变量不会被释放

	test02() = 1000;
	cout << "赋值操作1000给函数调用返回值a" << endl;
	cout << "ref2 = " << ref2 << endl;//函数调用做左值,并赋值操作,别名访问也是1000了
	cout << "ref2 = " << ref2 << endl;

	system("pause");
	return 0;
}

在这里插入图片描述

15.5 引用本质

本质在C++内部实现是一个指针常量
指针常量具体可见C++笔记指针
在这里插入图片描述

15.6 常量引用

作用:常量引用主要用来修饰形参,防止误操作

在函数形参列表中,可以加const修饰形参,防止形参改变实参
在这里插入图片描述
这里相当于指针常量第二种情况

16.函数提高

16.1 函数默认参数

在C++中,函数的形参列表中的形参是可以有默认值的
语法:返回值类型 函数名 (参数 = 默认值){}

#include<iostream>
using namespace std;
#include<string>

int function(int a,int b = 10, int c = 30) {//在函数定义时就给形参赋值,即默认参数
	
	return a + b + c;
}

//几点注意:
//1.如果某个位置参数有默认值,那么从这个位置往后都必须要有默认值  下面这样是错的
//int function(int a= 10, int b , int c = 30) {
//
//	return a + b + c;
//}


//2.如果函数声明有默认值,那么函数定义时就不能有默认参数
//即使暂时不报错,但是调用函数时就有问题了  声明和定义只能有一个是有默认参数的  否则编译器不知道听谁的 二义性
//int function(int a, int b = 10, int c = 30);

int main() {
	
	//函数的默认参数
	int res = function(10, 20);//如果实参有值,就用实参值,如果没有就用形参默认参数值,可以全部默认参数,这样实参可以不加,返回默认值
	//如果实参有定义值,形参也有定义,以实参的计算,没有的按形参默认参数值算
	//这里相当于a=10,b=20,c=30
	cout << res << endl;//60
	 
	system("pause");
	return 0;
}
  • 如果某个位置参数有默认值,那么从这个位置往后都必须要有默认值
  • 如果函数声明有默认值,那么函数定义时就不能有默认参数
  • 如果实参有定义值,形参也有定义,以实参的计算,没有的按形参默认参数值算

16.2 函数的占位参数

C++中函数的形参列表可以有占位参数,用来做占位,调用函数时必须填补该位置

语法:返回值类型 函数名(数据类型){}

#include<iostream>
using namespace std;
#include<string>

//占位参数
//返回值类型 函数名(数据类型)
//占位参数也可以有默认参数
//void func(int a,int ) {
void func(int a, int  =10) {

	cout << "this is a test" << endl;
}

int main() {

	//func(10, 10);//占位参数调用时也要填补实参
	func(10);//占位参数已经有了默认参数,这里可以省略
	system("pause");
	return 0;
}

16.3 函数重载

作用:函数名可以相同,提高复用性
函数重载满足条件:

  • 同一个作用域下
  • 函数名称相同
  • 函数参数类型不同 或者个数不同 或者顺序不同

注意:函数的返回值不可以作为函数重载的条件

16.3.1 基本语法

#include<iostream>
using namespace std;
#include<string>


//函数重载
//满足条件:
//1.同一个作用域下  //这里都在全局区
//2.函数名称相同
//3.函数参数类型不同 /个数不同 / 顺序不同

void func() {
	cout << "func的调用" << endl;//第一个
}


void func(int a) {
	cout << "func(int a)的调用" << endl;//第二个
}

//第一个和第二个参数个数不同,函数名相同,成为函数重载,可以

void func(double a) {
	cout << "func(double a)的调用" << endl;//第三个
}

//第二个和第三个参数类型不同,函数重载

void func(double a,int b) {
	cout << "func(double a,int b)的调用" << endl;//第四个
}

void func(int a, double b) {
	cout << "func(int a,double b)的调用" << endl;//第五个
}


//第四个和第五个参数顺序不同,也可

//注意:函数返回值不可以作为函数重载的条件
//int func(int a) {
//	cout << "func(int a)的调用" << endl;//第六个
//	return -1;
//}
//
//
//string func(int a) {
//	cout << "func(int a)的调用" << endl;//第七个
//	return "abs";
//}

//第六个和第七个都是不行的
//函数有返回值和无返回值不可构成函数重载,即函数返回值不可作为函数重载条件
//函数都有返回值,返回值类型不同也不可以作为函数重载条件

int main() {

	func();
	func(10);
	func(3.15);
	func(3.15, 10);
	func(10, 3.45);

	system("pause");
	return 0;
}

在这里插入图片描述

16.3.2 注意事项

  • 引用作为重载条件
  • 函数重载碰到函数默认参数
#include<iostream>
using namespace std;


//函数重载注意事项
//1.引用作为重载的条件
//这里二者参数类型不同  加/不加const可以作为重载条件
void func(int& a) {//不能int &a= 10;非法访问,必须引用的是一个合理的空间
	cout << "func(int& a)的调用" << endl;
}


void func(const int& a) {//可以const int &a = 10,常量,编译器优化,int temp = 10,const int &a = temp;
	cout << "func(const int& a)的调用" << endl;
}


//2.函数重载碰到默认参数
void func2(int a, int b = 10) {
	cout << "func2(int a, int b = 10)的调用" << endl;
}


void func2(int a) {
	cout << "func2(int a)的调用" << endl;
}

//以上func2()本身重载合理,但是调用时如果只给一个值,上面两个都可,出现二义性

int main() {
	int a = 10;
	func(a);
	cout << "func(a)是调用的func(int& a)函数" << endl;

	func(20);
	cout << "func(20)是调用的func(const int& a)函数" << endl;
	
	//func2(12);//函数重载又有默认参数,出现二义性,报错,尽量避免,可以传上面也可以传下面func2()函数,所以二义性报错

	system("pause");
	return 0;
}

在这里插入图片描述

第八篇笔记到此结束,C++基础学习会持续更新在C++学习笔记合集中,当作学习笔记复习,如果能帮助其他小伙伴就更好了。
笔记是看黑马程序C++时做的记录,笔记中如果有错误和改进的地方,欢迎大家评论交流,up up up!!!
学习原视频来自:黑马程序员C++从0到1

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值