【黑马程序员】C++函数、引用、文件操作、内存模型

20240220

函数

函数默认参数

  • 如果某个位置不传实参,那么该位置可以实用默认参数

  • 语法:返回值类型 函数名(参数类型 形参名=默认值)

  • 注意

    • 如果某个位置设置了默认参数,那么从左往右后面都必须有默认值
    ```cpp
    #include <iostream>
    
    using namespace std;
    
    int func(int a, int b=20, int c=30) {
    	return a + b + c;
    }
    
    int main(){
    	int a = 10;
    	cout << func(a) << endl;
    	return 0;
    }
    
    
    * 函数的声明和定义只能有一个地方有默认参数,否则就会报错,因为如果在声明时指定一个默认值,在实现时在指定一组默认值,此时就会出现二义性,程序不知道使用声明的默认值还是定义的默认值
    
    ```cpp
    #include <iostream>
    
    using namespace std;
    
    int func(int a, int b =10);
    
    //error: redefinition of default argument
    int func(int a, int b =10) { 
    	return a+b;
    }
    
    int main(){
    	cout <<func(1,1) << endl;
    	return 0;
    }
    

函数占位参数

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

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

  • 注意

    • 占位参数也可以有默认值,有默认值的时,在调用的时候实参位置传递的占位值也可以不用传
    #include <iostream>
    
    using namespace std;
    
    void func(int=1) {
    	cout << "占位参数测试" << endl;
    }
    
    int main(){
    	func(1);
        func();
    	return 0;
    }
    

函数重载

基本概述

  • 作用:函数名可以相同,提高复用性

  • 函数重载需要满足的条件

    • 同一个作用域下

    • 函数名相同

    • 参数列表不同,满足三者之一即可(参数类型不同,参数个数不同,参数顺序不同)

  • 注意:函数返回值类型不同,不可以作为函数重载的条件

#include <iostream>

using namespace std;

void func(int a, float b) {
	cout << a << b << endl;
}

// 参数个数不同
void func(int a) {
	cout << a << endl;
}

// 参数类型不同
void func(int a, int b) {
	cout << a << b << endl;
}

// 参数顺序不同
void func(float a, int b) {
	cout << a << b << endl;
}

int main() {
	int a = 1;
	float b = 1.1;
	func(a,b);
	func(a);
	func(a,a);
	func(b,a);
	return 0;
}

函数重载注意事项

  • 引用作为重载的条件
#include <iostream>

using namespace std;

// int& a = 1在语法中就是不合法的
void func(int& a) {
	cout << "func(int& a)" << endl;
}

// const int& a = 1;在c++内部会先转化为int temp=1; const int& a=temp;
void func(const int& a) {
	cout << "func(const int& a)" << endl;
}

int main(){
	func(1);
	int a = 1;
	func(a);
	return 0;
}
  • 函数重载碰到默认参数,由于不知道调用那个就会出现函数调用的二义性error: redefinition of 'func'
#include <iostream>

using namespace std;

void func(int a) {
	cout << "func(int a)" << endl;
}

void func(int a=1) {
	cout << "func(int a=1)" << endl;
}

int main(){
	func();     //调用出现二义性
	func(1); 
	return 0;
}

引用

引用的基本使用

  • 作用:给变量起别名

  • 语法:数据类型 &别名 = 原名

  • 注意:

    • 引用必须初始化,错误示例:int &b=a

    • 引用在初始化后,不可以改变

引用做函数参数

  • 作用:函数传参时,可以利用引用技术让形参修饰实参

  • 优点:可以简化指针修改实参

  • 代码示例:引用操作的实质是,参数引用形参是实参的别名,形参的操作就是在操作形参别名实参指向的变量

#include <iostream>

using namespace std;

void swap(int &a, int &b) {
	int temp = a;
	a = b;
	b = temp;
}

int main() {
	int a = 1;
	int b = 2;
	swap(a, b);
	cout << "a: " << a << endl;
	cout << "b: " << b << endl;
	return 0;
}

引用做函数的返回值

不要返回局部变量的引用

#include <iostream>

int& getLocalVariable() {
    int x = 10;
    return x;
}

int main() {
    int& ref = getLocalVariable();
    std::cout << ref << std::endl;    //第一次结果正确是因为编译器做了保留
    std::cout << ref << std::endl;    // 第二次结果错误,因为内存已经释放
    return 0;
}

函数的调用可以作为左值

  • 本质是别名的重新赋值
#include <iostream>

using namespace std;

int & test() {
	static int a = 10;	// 使用静态局部变量,存放在全局区,全局区上的数据在程序结束后系统释放
	return a;
}

int main() {
	int &ref = test();
	cout << ref << endl;
	cout << ref << endl;
	test() = 100;
	cout << ref << endl;
	cout << ref << endl;
}

引用的本质

  • 本质:引用的本质在c++的内部实现是一个指针常量,地址的指向是固定的,只能修改其中的值,不能修改指针的指向
#include <iostream>

using namespace std;

// c++内部发现是引用,转化为int* const ref
void func(int& ref){
	// ref是引用,转化为*ref=100
	ref=100;
}

int main(){
	int a=10;
	//自动转换为int* const ref=&a;指针常量是指针的指向不能改变,这也说明为什么引用不可更改
	int& ref=a;
	// 内部发现ref是引用,自动帮我们转化为*ref=20;
	ref=20;
	cout << "a: "<< a << endl;
	cout << "ref: "<< ref << endl;
	func(a);
	return 0;
}

常量引用

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

  • 在函数形参列表中,可以加const修饰形参,防止形参改变实参

#include <iostream>

using namespace std;

void printVal(const int& a) {
	// a= 1000;
	cout << "a: " << a << endl;
}

int main(){
	int a = 10;
	printVal(a);
	cout << a<< endl;
	return 0;
}

引用必须引用一块合法的内存空间

  • 直接这么引用是错误的
// error: non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int'
int& ref = 10;
  • 可以使用常量指针引用一个值
// 加上const之后,编译器将代码修改 int temp=10;const int& ref=temp; 
const int& ref = 10

文件操作

背景

  • 程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放

  • 通过文件可以将数据持久化

  • C++对文件操作需要引入头文件<fstream>

文件分类

  • 文本文件:文件以文本的ASCII码存储在计算机中

  • 二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不可阅读

操作文件的三大类

  • ofstream:写文件

  • ifstream:读操作

  • fstream:读写文件

文本文件

写文件

写文件步骤

  1. 包含头文件:#include <fstream>

  2. 创建流对象:ofstream ofs;

  3. 打开文件:ofs.open("文件路径", 打开方式1|打开方式2);

  4. 写数据:ofs<<"写入的数据";

  5. 关闭文件:ofs.close();

文件打开方式

  • 文件打开方式可以配合着"|“使用,例如二进制的写使用"ios::binary|ios::out”

在这里插入图片描述

代码示例

#include <iostream>
// 1 引入头文件
#include <fstream>
using namespace std;

void test(){
    // 2 创建流对象
	ofstream ofs;
    // 3 打开文件
	ofs.open("write.txt", ios::out);
    // 4 写文件
	ofs << "hello file" << endl;
    // 5 关闭文件
	ofs.close();
}

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

读文件

读文件步骤

  1. 包含头文件:#include <fstream>

  2. 创建流对象:ifstream ifs;

  3. 打开文件,并判断文件是否打开成功:

    1. ifs.open("文件路径", 打开方式1|打开方式2);

    2. if (!ifs.is_open()){cout << "open file fail";}

  4. 读数据:四种方式读写

  5. 关闭文件:ifs.close();

代码示例

#include <iostream>
#include <fstream>
#include <string>

using namespace std;

void test1(){
	cout << "=======方式一读========"<< endl;
	ifstream ifs;
	ifs.open("write.txt");
	if(!(ifs.is_open())){
		cout << "打开文件失败" << endl;
			return;
	}
	// 读文件1
	char buf[1024]={0};
	while(ifs>>buf){
		cout << buf<<endl;
	}
	ifs.close();
}
void test2(){
	cout << "=======方式二读========"<< endl;
	ifstream ifs;
	ifs.open("write.txt");
	if(!(ifs.is_open())){
		cout << "打开文件失败" << endl;
			return;
	}
	// 读文件2
	char buf[1024]={0};
	while(ifs.getline(buf, sizeof(buf))){
		cout << buf<<endl;
	}
	ifs.close();
}
void test3(){
	cout << "=======方式三读========"<< endl;
	ifstream ifs;
	ifs.open("write.txt");
	if(!(ifs.is_open())){
		cout << "打开文件失败" << endl;
			return;
	}
	// 读文件3
	string buf;
	while(getline(ifs, buf)){
		cout << buf<<endl;
	}
	ifs.close();
}

void test4(){
	cout << "=======方式四读========"<< endl;
	ifstream ifs;
	ifs.open("write.txt");
	if(!(ifs.is_open())){
		cout << "打开文件失败" << endl;
			return;
	}
	// 读文件4
	char c;
	while((c=ifs.get()) != EOF){
		cout << c;
	}
	ifs.close();
}
int main(){
	test1();
	test2();
	test3();
	test4();

}

写二进制文件

  • 参数原型:ofstream& write(const char* buf, int size);,参数一为写入内容的地址,参数二为写入数据的大小

写二进制文件步骤

  1. 包含头文件:#include <fstream>

  2. 创建流对象:ofstream ofs("文件路径", 打开方式1|打开方式2);,可以使用这种构造方法将创建和打开对象合并成一步进行

  3. 打开文件:ofs.open("文件路径", 打开方式1|打开方式2);

  4. 写数据:ofstream& write(const char* buf, int size);,参数一为写入内容的地址,参数二为写入数据的大小

  5. 关闭文件:ofs.close();

代码示例

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

struct Person{
	char name[10];
	int age;
};

void test(){
	ofstream ofs("binary.txt", ios::out|ios::binary);
	Person p = {"zsx", 19};
	ofs.write((const char *)&p, sizeof(Person));
	ofs.close();
}

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

读二进制文件

  • 参数原型:ifstream& read(char* buf, int len);,字符指针buf指向内存中的一段存储空间,len是读写的字节数

代码示例

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

struct Person{
	char name[10];
	int age;
};

void test(){
	ifstream ifs("binary.txt", ios::in|ios::binary);
	Person p;
	ifs.read((char *)&p, sizeof(Person));
	if (!ifs.is_open()){
		cout << "打开文件出错" << endl;
		return;
	}
	cout << p.name << endl;
	cout << p.age << endl;
	ifs.close();
}

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

20240209

内存分区模型

分区意义

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

代码区

  • 处于程序未执行之前

  • 程序编译后生成的可执行程序

  • 存放函数体的二进制代码,存放CPU执行的机器指令,由操作系统进行管理

  • 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可

  • 代码区是只读的,只读的原因是防止程序意外的修改了它的指令

全局区

特点

  • 处于程序未执行之前

  • 存放全局变量和静态变量

  • 全局区包含了常量区,字符串常量和其它常量也存放在此

  • 该区域的数据在程序结束后由操作系统释放

代码示例

  • 代码
#include <iostream>

using namespace std;

// 全局变量
int g_a = 20;
int g_b = 10;
// 全局常量
const int c_g_a = 10;
const int c_g_b = 10;

int main(){

	// 局部变量
	int a = 10;
	int b = 10;
	// 静态变量, 在局部变量前面加上static关键字
	static int s_a = 10;
	static int s_b = 10;
	// 局部常量,在局部变量前面加上const关键字
	const int c_a = 10;
	const int c_b = 10;
	cout << "局部变量a的地址是:" << &a << endl;
	cout << "局部变量b的地址是:" << &b << endl;
	cout << "静态局部变量a的地址是:" << &s_a << endl;
	cout << "静态局部变量b的地址是:" << &s_b << endl;
	cout << "局部常量c_a的地址是:" << &c_a << endl;
	cout << "局部常量c_b的地址是:" << &c_b << endl;
	cout << "字符串常量hello的地址是:" << &("hello") << endl;
	cout << "全局变量g_a的地址是:" << &g_a << endl;
	cout << "全局变量g_b的地址是:" << &g_b << endl;
	cout << "const修饰的全局常量c_g_a的地址是:" << &c_g_a << endl;
	cout << "const修饰的全局变量c_g_b的地址是:" << &c_g_b << endl;
	return 0;
}
  • 运行结果,在内存中的存放位置,通过地址可以看到存储的远近

在这里插入图片描述

栈区

特点

  • 处于程序运行后

  • 由编译器自动分配释放,存放函数的参数值,局部变量等

  • 注意:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

代码示例

#include <iostream>

using namespace std;

int * func(int b) {	// 形参存放在栈区
	int a = 10;	// 局部变量存放在栈区
	return &a;	// 返回局部变量地址
}

int main(){
	int *p = func(1);
	cout << *p <<endl;	// 第一次可以打印正确数字是因为编译器做了保留
	cout << *p <<endl;	// 第二次就会返回乱码
	return 0;
}

堆区

特点

  • 由程序员自己分配释放,若不释放,程序结束时,由操作系统回收

  • 在C++中主要利用new在堆区开辟内存

代码示例

#include <iostream>

using namespace std;

int * func() {
	// 利用new关键字可以将数据开辟到堆区
	// 指针p是局部变量放在栈上,指针保存的数据是放在堆区
	int *p = new int(10);
	return p;
}

int main() {
	// 在堆区开辟数据
	int *p = func();
	cout << *p << endl;
	cout << *p << endl;
	cout << *p << endl;
	return 0;
}

new 操作符

  • c++ 利用new操作符在堆区开辟数据

  • 堆区开辟的数据由程序员手动释放,释放利用操作符delete

  • 语法:new 数据类型

  • 利用new创建的数据,会返回改数据对应类型的指针

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

double_happiness

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

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

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

打赏作者

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

抵扣说明:

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

余额充值