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:读写文件
文本文件
写文件
写文件步骤
-
包含头文件:
#include <fstream>
-
创建流对象:
ofstream ofs;
-
打开文件:
ofs.open("文件路径", 打开方式1|打开方式2);
-
写数据:
ofs<<"写入的数据";
-
关闭文件:
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;
}
读文件
读文件步骤
-
包含头文件:
#include <fstream>
-
创建流对象:
ifstream ifs;
-
打开文件,并判断文件是否打开成功:
-
ifs.open("文件路径", 打开方式1|打开方式2);
-
if (!ifs.is_open()){cout << "open file fail";}
-
-
读数据:四种方式读写
-
关闭文件:
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);
,参数一为写入内容的地址,参数二为写入数据的大小
写二进制文件步骤
-
包含头文件:
#include <fstream>
-
创建流对象:
ofstream ofs("文件路径", 打开方式1|打开方式2);
,可以使用这种构造方法将创建和打开对象合并成一步进行 -
打开文件:
ofs.open("文件路径", 打开方式1|打开方式2);
-
写数据:
ofstream& write(const char* buf, int size);
,参数一为写入内容的地址,参数二为写入数据的大小 -
关闭文件:
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创建的数据,会返回改数据对应类型的指针