一、 内存分区

C++程序在执行时,内存大致方向分为4个区域,不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程。代码区和全局区是在程序运行前,栈区和堆区是在代码运行后

  • 代码区:存放函数体的二进制码,有操作系统进行管理
  • 全局区:存放全局变量和静态变量以及常量
  • 栈区:由编译器自动分配释放,存放函数的函数值,局部变量等。
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收

1.1 代码区

存放CPU执行的机器指令

  • 代码区是共享的,只需要在内存中有一份代码即可
  • 代码区是只读的,防止程序意外地修改了它的指令

1.2 全局区

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

#include <iostream>
using namespace std;
// 全局变量 ----- 全局区
int g_a = 0;
int g_b = 0;
// 全局常量 ----- 全局区
const int c_g_a = 0;
const int c_g_b = 0;
int main() {
	// 以下为全局区
	// 全局变量
	cout << "全局变量a的地址为" << (int)&g_a << endl;
	cout << "全局变量b的地址为" << (int)&g_b << endl;
	// 全局常量
	cout << "全局常量a的地址为" << (int)&c_g_a << endl;
	cout << "全局常量b的地址为" << (int)&c_g_b << endl;
	// 静态变量
	static int s_a = 0; // static修饰的是静态变量
	static int s_b = 0;
	cout << "静态变量a的地址为" << (int)&s_a << endl;
	cout << "静态变量b的地址为" << (int)&s_b << endl;
	// 字符串常量
	cout << "字符串常量a的地址为" << (int)&"Hallo world" << endl;


	// 以下为非全局区
	// 局部变量
	int l_a = 0;
	int l_b = 0;
	cout << "局部变量a的地址为" << (int)&l_a << endl;
	cout << "局部变量b的地址为" << (int)&l_b << endl;
	// 局部常量
	// const修饰的常量
	const int c_l_a = 0;
	const int c_l_b = 0;
	cout << "局部常量a的地址为" << (int)&l_a << endl;
	cout << "局部常量b的地址为" << (int)&l_b << endl;

	system("pause");
	return 0;

}
全局变量a的地址为1109844336
全局变量b的地址为1109844340
全局常量a的地址为1109830576
全局常量b的地址为1109830580
静态变量a的地址为1109844344
静态变量b的地址为1109844348
字符串常量a的地址为1109831072
局部变量a的地址为1807742644
局部变量b的地址为1807742676
局部常量a的地址为1807742644
局部常量b的地址为1807742676
请按任意键继续. . .

可以看到全局变量,全局常量,静态变量,字符串常量是存储在全局区,其他量存储在非全局区。

1.3 栈区

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

1.4 堆区

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

#include <iostream>
using namespace std;

int* func() {
	int* p = new int(10);  // new关键字在堆区域开辟地址
	return p;
}
int main() {
	
	cout << *func() << endl;
	cout << *func() << endl;
	cout << *func() << endl;
	system("pause");
	return 0;

}
10
10
10
请按任意键继续. . .

new操作符
利用new操作符开辟数据
堆区开辟的数据,有程序员手动开辟,手动释放,释放使用delete
利用new创建的数据,会返回该数据对应的类型的指针

#include <iostream>
using namespace std;

int* func() {
	int* p = new int(10);  // new关键字在堆区域开辟地址
	return p;
}
int main() {
	
	cout << *func() << endl;
	int* p = func();
	cout << *p << endl;
	// 删除操作
	delete p;
	// cout << *p << endl; 会报错,原因是p已经从堆区域删除
	system("pause");
	return 0;

}

new在堆区开辟一个数组

#include <iostream>
using namespace std;
int* test() {
	int *arr = new int[10]; // new创建数组
	for (int i = 0; i < 10; i++) {
		arr[i] = i; // 赋值 
	}
	for (int i = 0; i < 10; i++)
	{
		cout << arr[i] << endl;
	}
	return arr;
}

int main() {
	int *arr = test();
	delete[] arr; // 删除数组时要加上[]表示删除的是一个数组
	system("pause");
	return 0;

}
0
1
2
3
4
5
6
7
8
9
请按任意键继续. . .

二. 引用

2.1 引用的基本使用

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

#include <iostream>
using namespace std;


int main() {
	int a = 0;
	// 引用
	int& b = a;
	cout << "a=" << a<<endl;
	cout << "b=" << b<<endl;
	int* p = &a;
	cout << "p的值为:" << *p << endl;
	b = 10; // 同样也会修改a的值
	cout << "p的值为:" << *p << endl;
	cout << "a的值为:" << a << endl;

	cout << "a的地址为:" << (int)&a << endl;
	cout << "b的地址为:" << (int)&b << endl;
	system("pause");
	return 0;

}
a=0
b=0
p的值为:0
p的值为:10
a的值为:10
a的地址为:-289408796
b的地址为:-289408796
请按任意键继续. . .

注意事项

  • 引用必须初始化
  • 引用在创建之后就不可改变了
int &a; // 错误 未初始化!


int a = 0;
int c = 0;
// 引用
int& b = a;
int& b = c; // 报错,创建之后就不可以更改

2.2 引用做函数参数

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

#include <iostream>
using namespace std;
void swap(int &a,int&b) {
	int temp = a;
	a = b;
	b = temp;
}

int main() {
	int a = 10;
	int b = 20;
	cout << "交换前a=" << a << endl;
	cout << "交换前b=" << b << endl;
	swap(a, b);
	cout << "交换后a=" << a << endl;
	cout << "交换后b=" << b << endl;
	system("pause");
	return 0;

}
交换前a=10
交换前b=20
交换后a=20
交换后b=10
请按任意键继续. . .

引用传递和地址传递效果一样,都可以对实参进行修改。

2.3 引用做函数返回值

#include <iostream>
using namespace std;
// 不要返回局部变量的引用
//int &test() {
//	int a = 10;
//	cout <<"a的地址:"<< (int)& a << endl;
//	return a;
//}

int &test() {
	static int a = 10; // 静态变量  在全局区
	cout <<"a的地址:"<< (int)& a << endl;
	return a;
}
// 引用的格式
// int & a = b;
int main() {
	int& ref = test();

	cout << "ref的地址" << (int) & ref << endl;
	cout << "ref的地址为:" << ref << endl;
	system("pause");
	return 0;

}
a的地址:1526331140
ref的地址1526331140
ref的地址为:-858993460
请按任意键继续. . .

函数的调用可以作为左值

#include <iostream>
using namespace std;
int& test() {
	static int a = 10;// 静态变量
	return a;
}

int main() {
	int& b = test();
	cout << "b的值为:" << b<<endl;
	test() = 1000;  // 函数调用作为左值
	cout << "b的值为:" << b<<endl;
	system("pause");
	return 0;

}
b的值为:10
b的值为:1000
请按任意键继续. . .

2.4 引用的本质

引用的本质是在C++内部实现一个指针常量,由编译器内部完成
指针常量是指针的指向不可改变,但指针指向的值可以改变

#include <iostream>
using namespace std;
void test(int& x) {
	cout << x << endl;
}

int main() {
	int a = 0;
	int &b = a;  // 相当于指针常量 int const *b  = &a;  指向对象不能变,指向对象的值能变
	b = 10; // 相当于*b =10  解引用
	cout << "a的值为:" << a << endl;
	cout << "b的值为:" << b << endl;
	system("pause");
	return 0;

}
a的值为:10
b的值为:10
请按任意键继续. . .

2.5 常量引用

主要用来修饰形参,防止误操作,使用const修饰形参,防止形参修改实参

#include <iostream>
using namespace std;
void printValue(const int& a) {
	//a = 100; const修饰后不可再函数内部进行更改,防止误操作
	cout << a << endl;
}

int main() {
	int a = 10;
	printValue(a);
	system("pause");
	return 0;

}
10
请按任意键继续. . .

在写函数内部具体代码时,应该先判断是否需要使用const修饰,防止后续代码有误操作。

三. 函数提高

3.1 函数的默认参数

默认参数可以不传值,也可以重新传值进行覆盖。
注意事项:

  • 如果某个位置有默认参数,那么从这个默认参数后都应该有默认参数
  • 如果函数申明有默认参数,那么函数实现就不能有默认参数,两者只能有其一,防止重定义
#include <iostream>
using namespace std;
// 设置函数默认参数
int func(int a, int b = 20, int c = 30) {
	return a + b + c;
}
// 注意事项
// 1. 如果某个位置有默认参数,那么从这个默认参数后都应该有默认参数
// int a, int b = 20, int c; 错误

// 2.如果函数申明有默认参数,那么函数实现就不能有默认参数,两者只能有其一,防止重定义
//int func2(int a = 10, int b = 10);
//int func2(int a = 20, int b = 30) {
//	return a + b;
//}


int main() {
	cout << func(10,30) << endl;
	system("pause");
	return 0;

}
70
请按任意键继续. . .

3.2 占位参数

用于占位,调用函数时必须填补该位置,也可以设置默认参数,意义不大,但后续会用到。
语法: 返回值类型 函数名 (数据类型) {}

#include <iostream>
using namespace std;
void func(int a, int) {
	cout << a << endl;
}

int main() {
	func(1, 2);
	system("pause");
	return 0;

}
1
请按任意键继续. . .

四. 函数重载

作用:函数名可以相同,提高复用性
需要满足如下条件:

  1. 同一个作用域
  2. 函数名称相同
  3. 函数参数类型不同或者 个数不同 或者顺序不同
    函数的返回值不可作为函数重载的条件
#include <iostream>
using namespace std;

void func() {
	cout << "hallo world" << endl;
}
void func(int a) {
	cout << "hallo world" << endl;
}
int main() {
	func();
	system("pause");
	return 0;

}

抓住重载条件即可,函数参数类型不同或者 个数不同 或者顺序不同
引用作为函数重载条件

#include <iostream>
using namespace std;
void test1(int& a) {  // int&a =10 不合法
	cout << "调用的test1 int& a!" << endl;
}
void test1(const int& a) {// const int&a =10 不合法
	cout << "调用的test1! const int& a" << endl;
}


int main() {
	int a = 10;
	test1(a);
	test1(10);
	system("pause");
	return 0;

}
调用的test1 int& a!
调用的test1! const int& a
请按任意键继续. . .

函数重载遇到默认参数情况

#include <iostream>
using namespace std;

void test1(int a,int b=10) {
	cout << "调用int a,int b=10" << endl;
}
void test1(int a) {
	cout << "调用int a" << endl;
}
int main() {
	// test1(10); // 报错
	test1(10,20); // 不报错
	system("pause");
	return 0;

}
调用int a,int b=10
请按任意键继续. . .
01-09 10:34