C++核心编程
1.内存分区模型
分为4个区域:代码区(存放二进制代码)、全局区(存放全局变量、静态变量以及常量)、栈区(编译器自动分配释放)和堆区(程序员分配释放,程序结束时程序员还没有释放则由操作系统回收)
分区的意义:不同区域存放的数据,赋予不同的生命周期,会给我们带来更大的灵活编程。
(1)代码区:
特点是共享和只读。
(2)全局区:
包括全局变量、静态变量和常量,常量又分为字符串常量和其他常量(即const修饰的常量)。
该区域的数据在程序结束之后由操作系统来释放。
全局区中的数据:全局变量、静态变量(static关键字修饰的变量)、常量:字符串常量+const修饰的全局变量(全局常量);
不在全局区中的数据:局部变量、const修饰的局部变量(局部常量)。
#include <iostream>
using namespace std;
//全局变量
int g_a=10;
int g_b=10;
//const修饰的全局变量
const int c_g_a=10;
const int c_g_b=10;
int main()
{
//全局区
//全局变量、静态变量、常量
//创建普通局部变量
int a=10;
int b=10;
cout << "局部变量a的地址为:" << (long long) &a <<endl;
cout << "局部变量b的地址为:" << (long long) &b <<endl;
cout << "全局变量g_a的地址为:" << (long long) &g_a <<endl;
cout << "全局变量g_b的地址为:" << (long long) &g_b <<endl;
//静态变量 在普通变量的前面加static,属于静态变量
static int s_a=10;
static int s_b=10;
cout << "静态变量s_a的地址为:" << (long long) &s_a <<endl;
cout << "静态变量s_b的地址为:" << (long long) &s_b <<endl;
//常量
//字符串常量 双引号引起来的字符串
cout << "字符串常量的地址为:" << (long long)&"hello world" <<endl;
//const修饰的变量
//const修饰的全局变量 存放在全局区
cout << "全局常量c_g_a的地址为:" << (long long) &c_g_a <<endl;
cout << "全局常量c_g_b的地址为:" << (long long) &c_g_b <<endl;
//const修饰的局部变量
int c_l_a=10;
int c_l_b=10;
cout << "局部常量c_l_a的地址为:" << (long long) &c_l_a <<endl;
cout << "局部常量c_l_b的地址为:" << (long long) &c_l_b <<endl;
system("pause");
return 0;
}
(3)栈区:
存放函数的参数(形参)和局部变量,该区域的数据由编译器自动分配释放。
不要返回局部变量的地址,因为栈区的数据由编译器自动地释放。
#include <iostream>
using namespace std;
//栈区数据的注意事项 ---- 不要返回局部变量的地址
//栈区的数据由编译器管理开辟和释放
int* func(int b)//形参数据也会放在栈区
{
b=100;
int a=10;//局部变量 存放在栈区,栈区的数据在函数执行完后自动释放,再用指针去操纵那块内存,就已经是非法操作了
return &a;//返回局部变量的地址
}
int main()
{
int c=3;
int * p=func(c);
cout << *p <<endl;//第一次可以打印正确的数字,是因为编译器做了保留
cout << *p <<endl;//第二次这个数据就不再保留了
system("pause");
return 0;
}
(4)堆区:
由程序员来分配数据、管理释放,若程序员不释放,那么程序结束时由操作系统释放。
在c++中主要利用关键字new在堆区开辟内存。
在c++中,用关键字delete来释放堆区开辟的内存。
#include <iostream>
using namespace std;
int* func()
{
//利用new关键字可以将数据开辟到堆区
//定义的指针,本质上也是局部变量,放在栈区,指针保存的数据是放在堆区
int * p=new int(100);//会返回一个堆区的地址,所以要用指针变量来接收返回的地址
return p;
}
int main()
{
//在堆区开辟数据
int * p=func();
cout << *p << endl;
cout << *p << endl;
cout << *p << endl;
cout << *p << endl;
system("pause");
return 0;
}
#include <iostream>
using namespace std;
//1、new的基本语法
int * func()
{
//在堆区创建一个整型数据
//new返回的是该数据类型的指针,所以要用相应数据类型的指针变量来接收
int * p=new int(10);//10代表数字10,具体数据
return p;
}
void test01()
{
int * p=func();
cout << *p <<endl;//10
cout << *p <<endl;//10
cout << *p <<endl;//10
//堆区的数据由程序员管理开辟和释放
//如果想释放堆区的数据,利用关键字delete
delete p;
cout << *p <<endl;//14822096 //视频中该行代码会报错,因为内存已经被释放,再次访问就是非法操作,所以会报错
}
//2、在堆区利用new开辟一个数组
void test02()
{
//在堆区创建10个整型数据的数组
int * arr=new int[10];//10代表数组有10个元素 返回的是这块连续内存空间的首地址
for (int i = 0; i < 10; i++)
{
arr[i]=i+100;//赋值:100-109
}
for (int i = 0; i < 10; i++)
{
cout << arr[i] <<endl;
}
//释放数组
//释放数组的时候 要加[]才可以
delete[] arr;
}
int main()
{
// test01();
test02();
system("pause");
return 0;
}
2.引用(重要!!!)
(1)作用(本质):
给变量起别名
(2)语法:
数据类型 &别名=原名;
#include <iostream>
using namespace std;
int main()
{
//引用基本语法
//数据类型 &别名=原名
int a=10;
//创建引用
int &b=a;
cout << "a=" << a << endl;
cout << "b=" << b << endl;
b=100;
cout << "a=" << a << endl;
cout << "b=" << b << endl;
system("pause");
return 0;
}
(3)注意事项:
引用必须要初始化;(eg. int &b; 这是错误的,引用必须要初始化)
引用一旦初始化后就不可以更改了。
#include <iostream>
using namespace std;
int main()
{
//1、引用必须初始化
int a=10;
// int &b;//错误,必须要初始化
int &b=a;
//2、引用在初始化后,不可以改变
int c=20;
b=c;//赋值操作,而不是更改引用 该行代码的意思是:把c的值赋给b所指向的那块内存
cout << "a=" << a << endl;
cout << "b=" << b << endl;
cout << "c=" << c << endl;
system("pause");
return 0;
}
(4)引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参。
优点:可以简化指针修改实参。
通过引用参数产生的效果同地址传递是一样的,但引用的语法更简单。
#include <iostream>
using namespace std;
//交换函数
//1、值传递
void mySwap01(int a,int b)
{
int temp=a;
a=b;
b=temp;
// cout << "mySwap01中的a=" << a << endl;//20
// cout << "mySwap01中的b=" << b << endl;//10
}
//2、地址传递
void mySwap02(int *a,int *b)
{
int temp=*a;
*a=*b;
*b=temp;
}
//3、引用传递
void mySwap03(int &a,int &b)//形参a是实参a的别名,形参b是实参b的别名
{
int temp=a;
a=b;
b=temp;
}
int main()
{
int a=10;
int b=20;
mySwap01(a,b);
// cout << "a=" << a << endl;//10
// cout << "b=" << b << endl;//20
//值传递:实参并没有发生改变
// mySwap02(&a,&b);
// cout << "a=" << a << endl;//20
// cout << "b=" << b << endl;//10
//地址传递:形参会修饰实参,即实参的值会发生改变
mySwap03(a,b);
cout << "a=" << a << endl;//20
cout << "b=" << b << endl;//10
//引用传递:形参会修饰实参,即实参的值会发生改变
system("pause");
return 0;
}
(5)引用做函数的返回值
不要返回局部变量的引用;如果函数的返回值是引用,那么这个函数的调用可以作为左值。
#include <iostream>
using namespace std;
//引用做函数的返回值
//1、不要返回局部变量的引用
int& test01()
{
int a=10;//局部变量存放在四区中的 栈区
return a;
}
//2、函数的调用可以作为左值
int& test02()
{
static int a=10;//静态变量存放在全局区,全局区上的数据在程序结束后,由系统释放
return a;
}
int main()
{
// int &ref=test01();
// //与视频结果不一致,第一次结果正确是因为编译器做了保留;第二次结果错误是因为a的内存已经释放了,
// //所以再通过别名区操纵那块内存,就是一个非法的操作。
// cout << "ref=" << ref << endl;
// cout << "ref=" << ref << endl;
int &ref2=test02();
cout << "ref2=" << ref2 << endl;
test02()=1000;//如果函数的返回值是引用,那么这个函数的调用可以作为左值
cout << "ref2=" << ref2 << endl;
system("pause");
return 0;
}
(6)引用的本质:
在c++内部实现一个指针常量(指针的指向是不可以修改的,指针的值是可以改动的)。
c++推荐使用引用技术,因为语法方便,引用的本质是指针常量,但是所有指针操作编译器都帮我们做了。
int main()
{
int a=10;
//c++会自动将下面一行的代码转换为int* const ref=&a;(指针常量):指向不可以改,这也就说明为什么引用一旦初始化后就不能更改
int &ref=a;
ref=20;//内部发现reg是引用,就会自动帮我们转换为:*ref=20;
cout << "a=" << a << endl;
cout << "ref=" << ref << endl;
system("pause");
return 0;
}
(7)常量引用:
用const修饰形参,防止误操作。
//打印数据的函数
void showValue(const int &val)//const:用来修饰形参,防止误操作
{
// val=1000;//防止不小心修改了
cout << "val=" << val << endl;
}
int main() {
//常量引用
//使用场景:用来修饰形参,防止误操作
// int a=10;
// // int &ref=10;//报错 因为引用必须引一块合法的内存空间,不能是一个常数
// const int &ref=10;//加上const后,编译器将代码修改为:int temp=10; const int &ref=temp;
// // ref=20;//加入const之后编程只读,不可以修改
int a=100;
showValue(a);
cout << "a=" << a << endl;
system("pause");
return 0;
}
3.函数高级
(1)函数的默认参数
//函数默认参数
int func(int a,int b=20,int c=30)//给函数参数设置默认值,如果调用函数时给该参数赋了值,那就用赋的值进行计算,如果没有给它赋值,那就用默认值进行计算
{
return a+b+c;
}
//注意事项;
//1、如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须由默认值;
// int func2(int a=10,int b,int c,int d)//错误案例
// {
// return a+b+c+d;
// }
//2、如果函数声明有默认参数,那么函数实现就不能有默认参数了。
//即:声明和实现只能有一个有默认参数
// int func2(int a,int b);//函数的声明
// int func2(int a,int b)//函数的实现
// {
// return a+b;
// }
// //函数的声明和实现分开写的好处:函数的声明写在前面,使程序运行的时候知道有这么一个函数,需要调用该函数的时候就去后面找这个的实现
//但如果函数声明有默认参数,那么函数实现就不能有默认参数了
//即:声明和实现只能有一个有默认参数
// int func2(int a=10,int b=10);//函数的声明 错误案例
// int func2(int a=20,int b=10)//函数的实现 产生二义性
// {
// return a+b;
// }
int main()
{
cout << func(10,30) << endl;
// cout << func(10,20,30) << endl;//报错
system("pause");
return 0;
}
(2)函数的占位参数
c++中函数的形参列表里可以有占位参数,用来做占位,调用参数时必须填补该位置。
语法:返回值类型 函数名(数据类型){}
占位参数也可以有默认值。
//占位参数
//语法:返回值类型 函数名(数据类型){}
//目前阶段的占位参数 我们还用不到,后面课程中会用到
//占位参数还可以有默认参数
void func(int a,int=10)
{
cout << "this is a func" <<endl;
}
int main()
{
func(10);
system("pause");
return 0;
}
(3)函数重载:
函数名相同,提高复用性
函数重载需要满足的条件:
- 同一个作用域下;(如:全局作用域(就是函数都写在main函数外面)等)
- 函数名相同;
- 函数参数类型不同 或者 个数不同 或者 顺序不同。
注意:函数的返回值是不可以作为函数重载的条件的。
//函数重载
//可以让函数名相同,提高复用性
//条件:
//1、同一个作用域下;
//2、函数名称相同;
//3、函数参数类型不同 或 个数不同 或 顺序不同
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(1,3.14);
func(3.14,1);
system("pause");
return 0;
}
注意事项:
- 引用作为重载条件;
- 函数重载碰到函数默认参数。
//函数重载的注意事项:
//1、引用作为重载的条件
void func(int &a)//int &a=10;不合法
{
cout << "func(int &a)调用" <<endl;
}
void func(const int &a)//这两个函数的函数重载,属于类型不同 //const int &a=10;合法
{
cout << "func(const int &a)调用" <<endl;
}
//2、函数重载碰到函数默认参数
void func2(int a,int b=10)//调用时需要传入一个参数
{
cout << "func2(int a,int b)的调用" <<endl;
}
void func2(int a)//调用时需要传入一个参数 都需要传入一个参数就行,所以编译器不知道要调哪个函数,所以报错
{
cout << "func2(int a)的调用" <<endl;
}
int main()
{
// int a=10;
// func(a);//输出:func(int &a)调用 因为a是一个变量,可读可写,所以调用上面的那个函数
// func(10);//输出:func(const int &a)调用
// func2(10);//当函数重载碰到默认参数,会出现二义性,因而报错,所以我们要尽量避免
func2(10,20);//func2(int a,int b)的调用
system("pause");
return 0;
}