C++笔记(二)核心编程
内存分区模型
- 代码区:存放函数体的二进制代码,由操作系统管理(存放CPU执行的机器指令)(程序运行前就有了)
-
- 共享:由于会频繁的被执行,因此代码只需要存放一份共享
- 只读:防止程序意外修改内部指令
- 全局区:存放全局变量、静态变量和常量(字符串常量和const修饰的全局变量)(程序运行前就有了)
-
- 注:const修饰的局部变量(局部常量)不在全局区中
- 栈区:有编译器分配释放,存放函数的参数和局部变量等
-
- 注:在函数体中,不要返回局部变量的地址,因为函数执行完,在栈区的局部变量会自动释放,此时返回局部变量的地址可能会返回一个乱码
- 堆区:由程序员分配释放,若不释放,在程序结束时由操作系统回收。
-
- 注:可以使用new关键字在堆区创建变量(因此,在上面栈区的注意事项中,可以在函数体的变量中用new关键字(new返回的是指针,就是地址)在堆区创建变量,那么可以返回函数中的局部变量)
//new、delete关键字的使用
double * func(){ //返回double类型的指针
double * p = new double(10.0); //用new创建一个double类型的数据10.0
//new返回的是该数据类型的指针
return p; //这样使用new将数据创建在堆区中,堆区是我们自己管理,因
//此可以返回后不会释放
}
void test1(){
double * a = func(); //使用指正接受返回值
cout << *a << endl; //输出10.0
delete p; //手动释放这块堆区内存
//cout << *a << endl; //这时候访问返回的指针就是非法访问,会报错
}
void test2(){
int * arry = new int[10] //使用new创建一个数组,数组空间是10,就是10个数的数组
//注意定义数组使用的[]和上面定义变量使用的()
cout << arry[9] << endl; //这时候直接输出数组的第10个数值是没有数值的,就是这时候没有
//定义这个数组中的数值都是什么,可以用for循环给数组赋值
delete[] arry; //注意释放数组使用的[]
}
引用
基本语法
- 引用就是给一个变量起了一个别名,这个别名与原名是一块内存,因此可以互相修改改变内存值
-
- 引用的本质就是指针常量(指针的指向不可以改变),因此初始化后不可以再改变
- 语法:数据类型 &别名 = 原名
int a = 10; //定义一个变量
int &b = a; //使用引用&,这样b就是a的别名
b = 20; //这是对b进行操作,就是对a进行操作,a的值也会改变
注意事项
-
引用必须初始化
int &b //这是错误的
-
- 引用在初始化之后不可以改变
b = c //b作为a的引用,不可以改变为其他的变量c的引用
- 引用在初始化之后不可以改变
-
引用作为函数参数
-
- 可以用引用的技术让形参修饰实参,达到简化指针修改实参的效果
void myswap1(int a, int b){ //使用值传递的方式
int temp = a;
a = b;
b = temp;
}
void myswap2(int *a, int *b){ //使用地址传递的方式
int temp = *a;
*a = *b;
*b = temp;
}
void myswap3(int &a, int &b){ //使用引用作为形参的方式
int temp = a; //写法跟值传递的方式一样,但是实现效果跟地址传递一样
a = b;
b = temp;
}
int main(){
int a = 10;
int b = 20;
myswap1(); //值传递不会修改实参的值
myswap2(); //地址传递可以修改实参的值
myswap3(); //使用引用可以修改实参的值
//因为引用的别名跟原名使用的同一块内存
}
引用做函数返回值
- 引用不可以作为局部变量的返回值
- 使用引用作为函数的返回值时,函数的调用可以作为左值(就是说可以给这个函数的返回值赋值)
int& func(){
static int a = 20; //这是创建在全局区的变量,在函数执行完之后不会被释放。
//因此可以作为返回值返回
//int a = 10; //这是创建在栈区的局部变量,在函数执行完之后会被释放
//因此不可以作为返回值返回
return a;
}
int main(){
int& ref = func(); //需要使用引用的变量接收函数的返回值
func() = 1000; //引用的作为返回值的函数可以作为左值,成为被赋值的乙方
cout << ref << endl; //这时,ref输出的就是1000了
}
常量引用
- 为了防止函数中误操作形参导致实参修改的情况(跟const在函数中的使用一样)
void func(const int &a){ //用引用作为形参,防止函数修改此形参添加const
//a = 100; //因为形参a加了const,所以这里不可以修改
}
int main(){
const int &ref = 10; //这句可以运行,虽然并没有制定引用的原名是什么
//这句在编译时,会自动修改为两句:
//int temp = 10; const int &ref = temp;
ref = 20; //可以直接对引用操作
}
函数高级
默认参数
- 如果有我们自己传入的数据,那么就用自己的数据,如果没有,就用默认值
- 注:如果某个位置已经有了默认参数,那么这个位置之后,都必须有默认值
- 注:函数的声明和实现只能有一个有默认参数
int func1(int a, int b = 10, int c = 20){ //b有默认值,那么c必须也要有默认值
}
//调用时:
func1(10); //因为有默认值,可以不输入带有默认值的参数
func1(10,20); //也可以输入带有默认参数的值,b为20
int func2(int a = 10, int b = 20); //函数的默认参数在声明和实现只有一个存在
int func2(int a, int b){ //函数的声明有默认参数,实现就不需要了
}
占位参数
- 语法: 返回值类型 函数名(数据类型) {}
- 占位参数可以有默认参数
void func1(int a, int){} //形参中的int,就是占位参数
//后面会用到这个占位参数
void func2(int a, int = 10){} //占位参数可以有默认参数
int main(){
func(10,10); //占位参数没有变量,但是依然需要传入参数
func(10);
}
函数重载
- 可以让函数名相同,提高复用性
- 函数重载的条件
-
- 同一个作用域下
- 函数名称相同
- 函数的参数类型(int和const int也是不同的)不同,或者数量不同或者,顺序不同
- 返回值的类型不同不能作为函数重载的条件(换句话说,函数名可以相同,但是要根据参数让编译器分辨出你要执行的是那个函数)
void func(){} //函数重载名称可以相同,但是参数不能相同
void func(int a){}
void func(double a, int b){}
int func(double a, int b){} //不能将返回值作为重载的条件,因为编译器识别不出来
int main(){
func(); //执行的时候编译器会根据参数的不同使用不同的函数
func(10);
func(10.0,10);
}
- 函数重载注意事项
-
- 引用作为重载的条件
void func(int &a){} //引用作为重载
void func(const int &a){}
int main(){
int a = 10;
func(a); //这句会运行第一个函数,因为a是一个变量
func(10); //这句会运行第二个函数
//对第一个函数来说,实参给到形参相当于int &a = 10,不合法
//对第二个函数来说,实参给到形参相当于const int &a = 10,合法,在引用那里找,这句是合法的
}
-
- 函数重载碰到默认参数
void func(int a, int b = 10){} //尽量避免使用带有默认参数的函数重载
void func(int a){}
int main(){
func(10,20); //可以运行
func(10); //会出错,编译器不知道你要运行哪个
}