【c++核心编程】
1 内存模型
》代码区:存放函数体的二进制代码,由操作系统管理
》全局区:存放全局变量和静态变量及常量
》栈区:由编译器自动分配释放,存放函数的参数值、局部变量等
》堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。
1.1 程序运行前
在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域
代码区:
存放CPU执行的机器指令
代码区是共享的,目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。
代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令。
全局区:
全局变量和静态变量存放在此。
全局区还包含了常量区,字符串常量和其他常量也存放在此。
该区域的数据在程序结束后由操作系统释放。
总结:
C++中在程序运行前分为全局区和代码区
代码区的特点是共享和只读
全局区中存放全局变量、静态变量、常量
常量区中存放const修饰的全局常量和字符串常量
1.2 程序运行后
栈区:
由编译器自动分配存放,存放函数的参数值、局部变量等
注意事项:不要返回局部变量的地址。
堆区:
由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。
在C++中主要利用new在堆区开辟内存
1.3 new操作符
c++中利用new操作符在堆区开辟数据
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete
语法:new 数据类型
利用new创建的数据,会返回该数据对应的类型的指针
例如:int *p = new int(10); //在堆区创建一个int类型数据,值为10,地址返回给p
delete p;
int *p = new int[10]; //在堆区创建一个10个int类型元素的数组,首地址返回给p
delete[] p; //用delete释放数组,后面必须有[]
2 引用
2.1 引用的基本使用
作用:给变量起别名
语法:数据类型 &别名 = 原名
例如:int a = 10;
int &b = a;
2.2 引用的注意事项
引用必须初始化
引用在初始化后,不可以改变 声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型。因此引用本身不占存储单元,系统也不给引用分配存储单元。故:对引用求地址,就是对目标变量求地址。
不能建立数组的引用。因为数组是一个由若干元素所组成的集合,所以无法建立一个数组的别名。
2.3 引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参
在C语言中函数参数传递是值传递,如果有大块数据作为参数传递的时候,采用的方案往往是指针,因为这样可以避免将整块数据全部压栈,可以提高程序的效率。c++中又增加了一种同样有效率的选择,就是引用。
(1)传递引用给函数与传递指针的效果是一样的。 (2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。 (3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是在被调函数中同样要给形参分配存储单元,且需要重复使用“*指针变量名”的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须作用变量的地址作为实参。而引用更容易使用,更清晰。
例如:交换函数
//地址传递
void swap02(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
//引用传递
void swap03(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int a = 10;
int b = 20;
swap02(&a, &b);
swap03(a, b);
return 0;
}
2.4 引用做函数返回值
作用:引用可以作为函数的返回值存在
注意:不要返回局部变量引用
用法:函数调用作为左值
例如:
#include <iostream>
using namespace std;
//引用做函数的返回值
//1.不要返回局部变量的引用
int &test01()
{
int a = 10;
return a;
}
//2. 函数的调用可以作为左值
int &test02()
{
static int a = 10; //静态变量存放在全局区,全局区数据在程序结束后由系统释放
return a;
}
int main(void)
{
int &ref = test01();
int &ref1 = test02();
// cout << "ref = " << ref << endl; //第一次结果正确,因为编译器作了保留
// cout << "ref = " << ref << endl; //第二次结果错误,因为局部变量已经释放
cout << ref1 << endl;
test02() = 1000; //如果函数的返回值是引用,这个函数调用可以作为左值
cout << ref1 << endl;
test02() = 200;
cout << ref1 << endl;
system("pause");
return 0;
}
引用作为返回值,必须遵守以下规则:
(1)不能返回局部变量引用。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了“无所指”的引用,程序会进入未知状态。
(2)不能返回函数内部new分配的内存的引用。虽然不存在局部变量被动销毁的问题,可对于这种情况,又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
(3)可以返回类成员的引用,但最好是const。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
(4)引用与一些操作符的重载:
流操作符<<和>>,这两个操作符常常希望被连续作用,例如:cout << "hello" << endl;因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。
赋值操作符=。这个操作符象流操作符一样,是可以连续使用的。例如:x = j = 10;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符返回值的唯一选择。
2.5 引用的本质
本质:引用的本质在c++内部实现是一个指针常量
例如:
#include <iostream>
#using namespace std;
//发现是引用,转换为int *const ref = &a
void func(int &ref)
{
ref = 100; //ref是引用,转换为*ref = 100;
}
int main()
{
int a = 10;
//自动转换为int *const ref = &a; 指针常量的指向不可改变,也说明为佬引用不可更改
int &ref = a;
ref = 20; //内部发现ref是引用,自动转换为 *ref = 20;
cout << "a: " << a << endl;
cout << "ref: " << ref << endl;
func(a);
return 0;
}
2.6 常量引用
作用:常量引用主要用来修饰形参,防止误操作。
在函数形参列表中,可以加const修饰形参,防止改变实参。
例如:
#include <iostream>
using namespace std;
//打印数据的函数
void showValue(const int &val)
{
// val = 1000; 形参加const后,不能修改形参的值了
cout << "val = " << val << endl;
}
int main(void)
{
//常量引用 使用场景:用来修饰实参,防止误操作
int a = 100;
showValue(a);
cout << "a = " << a << endl;
system("pause");
return 0;
}
3 函数提高
3.1 函数默认函数
在c++中,函数的形参列表中的形参是可以有默认值的。
例如:
#include <iostream>
using namespace std;
//函数默认参数
int func(int a, int b = 20, int c = 30)
{
return a + b + c;
}
int main(void)
{
cout << func(10, 30) << endl;
system("pause");
return 0;
}
说明:
如果传入了实参数据,就用传入的。如果没有传入,则使用默认值
语法:返回值类型 函数名(形参 = 默认值)
注意事项:
1.如果形参某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值
例如:(错误)
int func(int a=10; int b, int c);
2.如果函数声明有默认参数,函数的实现就不能有默认参数;声明和实现只能有一个有默认参数
例如:
int func(int a=10; int b=10);
int main()
{
... ...
return 0;
}
int func(int a, int b)
{
return a+b;
}
3.2 函数占位参数
c++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
语法:返回值类型 函数名 (数据类型);
例如:
void func(int, int)
{
cout << "hello" << endl;
}
int main()
{
func(10, 10);
return 0;
}
3.3 函数重载
3.3.1 函数重载概述
作用:函数名可以相同,提高复用性
函数重载满足条件:
》同一个作用域下
》函数名相同
》函数参数类型不同、或者个数不同、或者顺序不同
注意:函数的返回值不能重载作为函数重载的条件
例如:
#include <iostream>
using namespace std;
void func()
{
cout << "func的调用" << endl;
}
//参数个数不同
void func(int a)
{
cout << "func(int a)的调用" << endl;
}
//参数类型不同
void func(char a, int b)
{
cout << "func(char a, int b)的调用" << endl;
}
//参数顺序不同
void func(int b, char a)
{
cout << "func(int b, char a)的调用" << endl;
}
int main(void)
{
func();
func(2);
func('c', 10);
func(10, 'a');
system("pause");
return 0;
}
3.3.2 函数重载的注意事项
》引用作为重载条件
》函数重载碰到函数默认参数 容易出现二义性,报错。
4. 类和对象
c++面向对象的三大特性:封装、继承、多态
c++认为万事万物皆为对象,对象上有其属性和行为
例如:
人可以作为对象,属性有姓名、年龄、身高等,行为有走、跑、跳等
车也可以作为对象,属性有轮胎、车灯等,行为有载人、放音乐等
具有相同性质的对象,可以抽象称为类,人属于人类,车属于车类
4.1 封装
4.1.1 封装的意义
封装是c++面向对象的三大特性之一
封装的意义
》 将属性和行为作为一个整体,表现生活中的事物
》 将属性和行为加以权限控制
封装意义一:
在设计类的时候,属性和行为写在一起表现事物
语法:class 类名 {访问权限:属性 / 行为 }
示例:
#include <iostream>
using namespace std;
const double PI = 3.14;
//设计一个圆类,求圆的周长 公式:2*PI*半径(半径作为圆类的属性)
class Circle
{
//访问权限
public: //公共权限
//属性 半径
int m_r;
//行为 获取圆的周长
double calculateZC()
{
return 2 * PI * m_r;
}
};
int main(void)
{
//通过圆类,创建具体的圆(对象)实例化(通过一个类,创建一个对象)
Circle c1;
//给圆对象的属性进行赋值
c1.m_r = 10;
cout << "圆的周长为: " << c1.calculateZC() << endl;
system("pause");
return 0;
}
示例二:
#include <iostream>
using namespace std;
//设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学号和姓名
class Student
{
//类中的属性和行为 统一称为成员
//访问权限
public:
//属性 成员属性 成员变量
string m_Name; //姓名
int m_ID; //学号
//行为 成员函数 成员方法 显示
void print()
{
cout << "姓名:" << m_Name << "\t" << "学号: " << m_ID << endl;
}
//给姓名赋值
void setName(string s)
{
m_Name = s;
}
//给学号赋值
void setID(int n)
{
m_ID = n;
}
};
int main(void)
{
Student s1;
s1.setName("张三");
s1.setID(20201001);
s1.print();
Student s2;
s2.setName("李四");
s2.setID(20201002);
s2.print();
system("pause");
return 0;
}
封装意义二:
类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
》 public 公共权限 类外可以访问
》 protected 保护仅限 类外不可以访问 类继承时儿子类可以访问父类中保护的内容
》 private 私有权限 类外不可以访问 类继承时儿子类不可以访问父类的私有内容
示例三:
#include <iostream>
using namespace std;
class Person
{
//公共权限
public:
string m_Name; //姓名
//保护权限
protected:
string m_Car; //汽车
//私有权限
private:
int m_Password; //银行卡密码
//类内都可以访问
public:
void func()
{
m_Name = "张三";
m_Car = "拖拉机";
m_Password = 123456;
}
};
int main(void)
{
Person p1;
p1.m_Name = "李四";
//p1.m_Car = "奔驰"; 保护权限内容,类外不能访问
//p1.mPassword = 147258; 私有权限内容,类外不能访问
p1.func();
system("pause");
return 0;
}
4.1.2 struct 和 class 的区别
在c++中 struct 和 class 唯一的区别就在于默认的访问权限不同
区别:
》 struct 默认权限为公共
》 class 默认权限为私有
例如:
#include <iostream>
using namespace std;
class c1
{
int m_A; //默认是私有权限
}
struct c2
{
int m_A; //默认权限为公共
}
int main()
{
c1 cC;
//cC.m_A = 100; 私有权限内容,类外不能访问,报错
c2 sC;
sC.m_A = 105;
return 0;
}
4.1.3 将成员属性设置为私有
优点1:将所有成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,可以检测数据的有效性。
#include <iostream>
#include <string>
using namespace std;
/*
成员属性设置为私有
1、可以自己控制读写权限
2、对于写可以检测数据的有效性
*/
class Person
{
public:
//设置姓名
void setName(string name)
{
m_Name = name;
}
//获取姓名
string getName()
{
return m_Name;
}
//获取年龄
int getAge()
{
return m_Age;
}
//设置年龄 可读可写 如果想修改年龄范围只能在0-100之间
void setAge(int age)
{
if (age < 0 || age > 100)
{
m_Age = 0;
cout << "输入的年龄超出范围!" << endl;
return;
}
m_Age = age;
}
//设置情人 只写
void setLover(string lover)
{
m_Lover = lover;
}
private:
string m_Name; //姓名 可读可写
int m_Age; //年龄 只读
string m_Lover; //情人 只写
};
int main(void)
{
Person p;
p.setName("张三");
p.setLover("苍井");
p.setAge(1000);
cout << "姓名:" << p.getName() << endl;
cout << "年龄:" << p.getAge() << endl;
system("pause");
return 0;
}
综合案例:
/*
案例:设计立方体类,求出立方体的面积和体积
分别用全局函数和成员函数判断两个立方体是否相等
*/
#include <iostream>
using namespace std;
class Cube
{
public:
//设置长
void setL(int n)
{
m_L = n;
}
//获取长
int getL()
{
return m_L;
}
//设置宽
void setW(int n)
{
m_W = n;
}
//获取宽
int getW()
{
return m_W;
}
//设置高
void setH(int n)
{
m_H = n;
}
//获取高
int getH()
{
return m_H;
}
//获取立方体面积
int calculateS()
{
return 2 * m_H + 2 * m_L + 2 * m_W;
}
//获取立方体体积
int calculateV()
{
return m_W * m_L * m_H;
}
//成员函数判断两个立方体是否相等
bool isSameByClass(Cube &c)
{
if ( m_L == c.getL() && m_W == c.getW() && m_H == c.getH() )
{
return true;
}
else
{
return false;
}
}
private:
int m_L; //长
int m_W; //宽
int m_H; //高
};
//全局函数判断两个立方体是否相等
bool isSame(Cube &c1, Cube &c2)
{
if (c1.getL() == c2.getL() && c1.getW() == c2.getW() && c1.getH() == c2.getH())
{
return true;
}
else
{
return false;
}
}
int main(void)
{
Cube c1;
c1.setH(10);
c1.setL(10);
c1.setW(10);
cout << "c1的面积: " << c1.calculateS() << endl;
cout << "c1的体积: " << c1.calculateV() << endl;
cout << endl << "===============================" << endl;
Cube c2;
c2.setH(10);
c2.setL(10);
c2.setW(12);
cout << "c2的面积: " << c2.calculateS() << endl;
cout << "c2的体积: " << c2.calculateV() << endl;
cout << endl << "===============================" << endl;
cout << "全局函数判断:" << endl;
bool ret = isSame(c1, c2);
if (ret)
{
cout << "c1和c2相等!" << endl;
}
else
{
cout << "c1和c2不相等!" << endl;
}
cout << endl << "===============================" << endl;
cout << "成员函数判断:" << endl;
ret = c1.isSameByClass(c2);
if ( ret )
{
cout << "c1和c2相等!" << endl;
}
else
{
cout << "c1和c2不相等!" << endl;
}
cout << endl << "===============================" << endl << endl;
system("pause");
return 0;
}