一、内存分区
C++程序在执行时,将内存大方向划分为4个区域
-
代码区:存放函数体的二进制代码,由操作系统进行管理的
-
全局区:存放全局变量和静态变量以及全局常量和字符串常量
-
栈区:由编译器自动分配释放, 存放函数的参数值,局部变量,局部常量等
-
堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
#include <iostream>
#include <string>
using namespace std;
//全局区
int g_a = 10; //全局变量
int g_b = 20;
const int c_g_a = 10; //全局常量
const int c_g_b = 20;
void globel_area() {
int a = 10; //局部变量
int b = 20;
const int c_a = 10; //局部常量
const int c_b = 20;
static int s_a = 10; //局部静态变量
static int s_b = 20;
cout << "局部变量a地址为: " << (int)&a << endl;
cout << "局部变量b地址为: " << (int)&b << endl;
cout << "全局变量g_a地址为: " << (int)&g_a << endl;
cout << "全局变量g_b地址为: " << (int)&g_b << endl;
cout << "静态变量s_a地址为: " << (int)&s_a << endl;
cout << "静态变量s_b地址为: " << (int)&s_b << endl;
cout << "字符串常量地址为: " << (int)&"hello world" << endl;
cout << "字符串常量地址为: " << (int)&"hello world1" << endl;
cout << "全局常量c_g_a地址为: " << (int)&c_g_a << endl;
cout << "全局常量c_g_b地址为: " << (int)&c_g_b << endl;
cout << "局部常量c_a地址为: " << (int)&c_a << endl;
cout << "局部常量c_b地址为: " << (int)&c_b << endl;
}
由此可见,局部常量并不在全局区。它存放在栈区或常量区。
#include <iostream>
#include <string>
using namespace std;
//栈区和堆区
int* func1() {
int a=10;
return &a; //返回内存地址
}
int* func2()
{
int* b = new int(10); //使用new在C++中分配的内存返回的是内存地址
return b;
}
void heap_area() {
int* a = func1();
cout << *a << endl; //调动栈区函数,函数结束后系统自动释放
cout << *a << endl;
int* b = func2();
cout << *b << endl; //调动堆区函数,函数结束后仍然存放在栈区
cout << *b << endl;
delete b; //手动调用关键字delete释放内存
int* arr = new int[10]; //new开辟数组
delete[] arr; //delete释放数组
}
int main() {
heap_area();
system("pause");
return 0;
}
此代码中两次调用func1和func2函数。栈区的第一次没有问题,但函数 func1
在第一次调用后被系统自动释放了,但是它返回的内存地址仍然保存在变量 a
中。因此,当第二次调用 func1
时,它实际上返回的是第一次调用时分配给变量 a
的内存地址。而堆区的两次由于没有手动释放,所以都打印正常。另外,不应该尝试使用delete释放函数 func1() 返回的地址,因为这块内存已经被释放了。正确的做法是避免使用这个返回的指针,或者确保这个指针指向的内存是可以通过 new
分配的。
注意事项:栈区不要返回局部变量的地址,栈区开辟的数据由编译器自动释放。另外,在C++中,使用new
和delete
进行动态内存分配和释放时,必须确保分配的内存大小正确,否则可能会导致内存溢出或分配不足的问题。此外,由于堆区的内存不是自动释放的,因此必须小心处理内存泄漏问题。
二、引用
语法: 数据类型 &别名 = 原名
本质:引用的本质在c++内部实现是一个指针常量.
注意事项:
-
引用必须初始化
-
引用在初始化后,不可以改变
-
引用作为函数参数时,形参会修饰实参
-
不要返回局部变量引用
#include <iostream>
#include <string>
using namespace std;
//1. 值传递
void mySwap01(int a, int b) {
int temp = a;
a = b;
b = temp;
}
//2. 地址传递
void mySwap02(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
//3. 引用传递
void mySwap03(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
//引用做函数返回值
int& test01() {
int a = 10; //局部变量
return a;
}
//返回静态变量引用
//静态变量存储与全局区,存放于栈区函数释放不会释放静态变量,会在整个程序执行完释放全局区数据
int& test02() {
static int a = 20;
return a;
}
//引用
void quote() {
int a = 10;
int b = 20;
mySwap01(a, b); //值传递不会改变实参
cout << "a:" << a << " b:" << b << endl;
mySwap02(&a, &b); //地址传递对应变量内存地址,会改变实参
cout << "a:" << a << " b:" << b << endl;
mySwap03(a, b); //引用传递本质是指针常量也会改变实参
cout << "a:" << a << " b:" << b << endl;
//不能返回局部变量的引用。因为test01函数是存于栈区的,一次调用后,系统自动释放
int& ref1 = test01();
cout << "ref1 = " << ref1 << endl;
cout << "ref1 = " << ref1 << endl;
//如果函数做左值(具有有效内存地址的实体),那么必须返回引用
//可以返回static静态变量的引用
int& ref2 = test02();
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
test02() = 1000;
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
//int& ref = 10; 引用本身需要一个合法的内存空间,因此这行错误
int c = 10;
int& quote_a = c;//是正确的
//加入const就可以了,编译器优化代码,int temp = 10; const int& ref = temp;相当于常量指针常量
const int& ref = 10;
//ref = 100; //加入const后不可以修改变量
cout << "c = " <<quote_a<< endl;
cout << "ref = "<<ref << endl;
//函数中利用常量引用防止误操作修改实参
}
int main() {
quote();
system("pause");
return 0;
}
三、函数
1.函数定义
- 1、返回值类型
- 2、函数名
- 3、参数表列
- 4、函数体语句
- 5、return 表达式
返回值类型 函数名 (参数列表)
{
函数体语句
return表达式
}
2.函数的调用和值传递
void swap(int num1, int num2)
{
cout << "交换前:" << endl;
cout << "num1 = " << num1 << endl;
cout << "num2 = " << num2 << endl;
int temp = num1;
num1 = num2;
num2 = temp;
cout << "交换后:" << endl;
cout << "num1 = " << num1 << endl;
cout << "num2 = " << num2 << endl;
//return ; 当函数声明时候,不需要返回值,可以不写return
}
int main() {
int a = 10;
int b = 20;
swap(a, b);//函数调用
cout << "mian中的 a = " << a << endl;
cout << "mian中的 b = " << b << endl;
system("pause");
return 0;
}
注意:值传递时,如果形参发生,并不会影响实参
3.函数声明
-
函数的声明可以多次,但是函数的定义只能有一次
4.函数默认参数和占位参数
- 在C++中,函数的形参列表中的形参是可以有默认值的。
语法:返回值类型 函数名 (参数= 默认值){}
- C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
语法: 返回值类型 函数名 (数据类型){}
int func(int a, int b = 10, int c = 10) {
return a + b + c;
}
//1. 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
//2. 如果函数声明有默认值,函数实现的时候就不能有默认参数
int func2(int a = 10, int b = 10);
int func2(int a, int b) {
return a + b;
}
//函数占位参数 ,占位参数也可以有默认参数
void func3(int a, int) {
cout << "this is func" << endl;
}
int main() {
cout << "ret = " << func(20, 20) << endl;
cout << "ret = " << func(100) << endl;
cout << func3(10,10);<< endl;//占位参数必须填补
system("pause");
return 0;
}
5.函数重载
**作用:**函数名可以相同,提高复用性
函数重载满足条件:
-
同一个作用域下
-
函数名称相同
-
函数参数类型不同 或者 个数不同 或者 顺序不同
注意: 函数的返回值不可以作为函数重载的条件
//函数重载需要函数都在同一个作用域下
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(int a ,double b)
{
cout << "func (int a ,double b) 的调用!" << endl;
}
void func(double a ,int b)
{
cout << "func (double a ,int b)的调用!" << endl;
}
//函数返回值不可以作为函数重载条件
//int func(double a, int b)
//{
// cout << "func (double a ,int b)的调用!" << endl;
//}
int main() {
func();
func(10);
func(3.14);
func(10,3.14);
func(3.14 , 10);
system("pause");
return 0;
}
函数重载注意事项
-
引用作为重载条件
-
函数重载碰到函数默认参数
//函数重载注意事项
//1、引用作为重载条件
void func(int &a)
{
cout << "func (int &a) 调用 " << endl;
}
void func(const int &a)
{
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;
}
int main() {
int a = 10;
func(a); //调用无const
func(10);//调用有const
//func2(10); //碰到默认参数产生歧义,需要避免
system("pause");
return 0;
}
四、文件操作
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放
通过文件可以将数据持久化
C++中对文件操作需要包含头文件 ==< fstream >==
文件类型分为两种:
-
文本文件 - 文件以文本的ASCII码形式存储在计算机中
-
二进制文件 - 文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们
操作文件的三大类:
-
ofstream:写操作
-
ifstream: 读操作
-
fstream : 读写操作
文件打开方式:
打开方式 | 解释 |
ios::in | 为读文件而打开文件 |
ios::out | 为写文件而打开文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 追加方式写文件 |
ios::trunc | 如果文件存在先删除,再创建 |
ios::binary | 二进制方式 |
1.文本文件
#include <fstream>
#include <iostream>
#include <string>
void test01()
{
ofstream ofs;
ofs.open("test.txt", ios::out);
ofs << "姓名:张三" << endl;
ofs << "性别:男" << endl;
ofs << "年龄:18" << endl;
ofs.close();
}
void test02()
{
ifstream ifs;
ifs.open("test.txt", ios::in);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}
//第一种方式
//char buf[1024] = { 0 };
//while (ifs >> buf)
//{
// cout << buf << endl;
//}
//第二种
//char buf[1024] = { 0 };
//while (ifs.getline(buf,sizeof(buf)))
//{
// cout << buf << endl;
//}
//第三种
//string buf;
//while (getline(ifs, buf))
//{
// cout << buf << endl;
//}
char c;
while ((c = ifs.get()) != EOF)
{
cout << c;
}
ifs.close();
}
int main() {
test01();
test02();
system("pause");
return 0;
}
2.二进制文件
- 二进制方式写文件主要利用流对象调用成员函数write
函数原型 :ostream& write(const char * buffer,int len);
- 二进制方式读文件主要利用流对象调用成员函数read
函数原型:istream& read(char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
#include <fstream>
#include <iostream>
#include <string>
class Person
{
public:
char m_Name[64];
int m_Age;
};
//二进制文件 写文件
void test01()
{
//1、包含头文件
//2、创建输出流对象
ofstream ofs("person.txt", ios::out | ios::binary);
//3、打开文件
//ofs.open("person.txt", ios::out | ios::binary);
Person p = {"张三" , 18};
//4、写文件
ofs.write((const char *)&p, sizeof(p));
//5、关闭文件
ofs.close();
}
void test02()
{
ifstream ifs("person.txt", ios::in | ios::binary);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
}
Person p;
ifs.read((char *)&p, sizeof(p));
cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl;
}
int main() {
test01();
test02();
system("pause");
return 0;
}