
重新系统学习c++语言,并将学习过程中的知识在这里抄录、总结、沉淀。同时希望对刷到的朋友有所帮助,一起加油哦!
本章是继上篇c++基础之后的c++核心。
生命就像一朵花,要拼尽全力绽放!
系列文章列表:
c++编程基础_森林女神的博客-CSDN博客_c++项目总结
1 程序内存分区模型c++程序内存分区:
| 区域 | 存放内容 | 管理方式 |
| 代码区 | 存放代码的二进制代码 | 由 *** 作系统进行管理 |
| 全局区 | 存放全局变量、静态变量、常量 | 由 *** 作系统进行管理,该区域的数据在程序结束后由 *** 作系统释放。 |
| 栈区 | 函数的参数、局部变量等 | 由编译器自动对内存分配和释放 |
| 堆区 | 利用new *** 作符开辟内存 | 由程序员分配和释放。若程序员不主动释放,程序结束由 *** 作系统释放 |
四区存在意义:
不同区域存放的数据,赋予不同生命周期,使编程更灵活。
1.1 程序运行前在程序编译后会生成可执行程序,在未执行该程序前,分为两个区域:
代码区:
存放CPU执行的机器指令
代码区有两个特点:
是共享的。目的是对于可能被多次执行的程序,只需要在内存中有一份代码即可。
是只读的。原因是防止程序被意外修改。
全局区:
存放全局变量、静态变量
还包含一些常量:字符串常量、const修饰的全局常量。(const 修饰的局部常量不在该区)
1.2 程序运行后
| 栈区 | 函数的参数、局部变量等 | 由编译器自动对内存分配和释放 |
| 堆区 | 利用new *** 作符开辟内存 | 由程序员分配和释放。若程序员不主动释放,程序结束由 *** 作系统释放 |
注意事项:
不要返回局部变量的地址,因为栈区的数据由系统自动释放,使用了可能会出错。
栈区示例:
#include
#include
using namespace std;
int* test() {
// 局部变量 存放栈区,在函数执行完后系统自动释放
int a = 10;
// 返回局部变量地址
return &a;
}
int main()
{
int* p = test();
cout << *p << endl; // 第一次打印正确,因为编译器做了保留
cout << *p << endl; //第二次使用数据就不再保留了
cout << *p << endl;
system("pause");
return 0;
}
堆区示例:
#include
#include
using namespace std;
int* test() {
// 利用new关键字,可以将数据开辟到堆区
// 指针本质也是局部变量,放在栈上,但指针保存的数据是堆区地址
int* p = new int(10);
return p;
}
int main()
{
int* p = test();
// 三次打印都是正确的
cout << *p << endl;
cout << *p << endl;
cout << *p << endl;
system("pause");
return 0;
}
1.3 new *** 作符
作用:在堆区开辟数据
特点:
- 只能在堆区开辟数据;
- 由程序员手动开辟,手动释放;
- 释放利用 *** 作符delete;
语法:
new 数据类型
利用new创建的数据,返回的是数据类型的指针。
示例1:基本语法
#include
#include
using namespace std;
int* test() {
int* p = new int(10);
return p;
}
int main()
{
int* p = test();
// 三次打印都是正确的
cout << *p << endl;
cout << *p << endl;
cout << *p << endl;
delete p;
// cout << *p << endl; // 内存已被delete释放,再次访问就是非法操作,报错
system("pause");
return 0;
}
示例2:new 开辟数组
#include
#include
using namespace std;
//在堆区利用new开辟数组
void test() {
// 创建10个元素的整型数组
int* arr = new int[10];
for (int i = 0; i < 10; i ++) {
arr[i] = i;
}
for (int i = 0; i < 10; i++) {
cout << arr[i]<
2 c++引用
2.1 引用的基本使用
作用:给变量起别名。可以跟变量一样 *** 作数据。
语法:数据类型& 别名 = 原名
示例:
#include
#include
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;
// 从地址打印可以看出a和b指向的都是同一块内存
cout << "a的地址为:" << &a << endl;
cout << "b的地址为: " << &b << endl;
system("pause");
return 0;
}
2.2 引用注意事项
1、引用在定义时必须初始化。
例如 int& b;//是错误
2、引用一旦初始化后,就不可以更改,即不能修改为其他变量的别名。
示例:
#include
#include
using namespace std;
int main()
{
int a = 10;
int b = 20;
//int& c; // 错误,引用必须在定义时初始化
int& c = a;// 一旦初始化后,就不可以更改
c = b;// 这是赋值 *** 作,不是更改引用
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
// 从地址打印可以看出a和c指向的都是同一块内存,没有更改c的引用
cout << "a的地址为:" << &a << endl;
cout << "b的地址为: " << &b << endl;
cout << "c的地址为:" << &c << endl;
system("pause");
return 0;
}
2.3 引用做函数参数
作用:函数传参数时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参
示例:
#include
#include
using namespace std;
// 1 值传递
void swap01(int a,int b) {
int tmp = a;
a = b;
b = tmp;
}
// 2 地址传递
void swap02(int* a, int* b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
// 3 引用传递
void swap03(int& a, int& b) {
int tmp = a;
a = b;
b = tmp;
}
void testSwap01() {
cout << "testSwap01:" << endl;
int a = 10;
int b = 20;
swap01(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
}
void testSwap02() {
cout << "testSwap02:" << endl;
int a = 10;
int b = 20;
swap02(&a, &b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
}
void testSwap03() {
cout << "testSwap03:" << endl;
int a = 10;
int b = 20;
swap03(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
}
int main()
{
testSwap01();
testSwap02();
testSwap03();
system("pause");
return 0;
}
总结:通过引用传参和按地址传参可以得到一样的效果,但是引用传参更简单方便。
2.4 引用做函数返回值作用:引用可以作为函数的返回值
注意:
- 不要返回局部变量的引用
- 若函数返回的是引用,则函数调用可以作为左值
示例:
#include
#include
using namespace std;
// 函数返回引用
int& test() {
static int a = 10;// 全局变量 在程序结束后才由系统释放
return a;
}
int main()
{
int& ref = test();
cout << "ref = " << ref << endl;
cout << "ref = " << ref << endl;
// 函数返回的是引用,函数调用可以作为左值
test() = 2000;
cout << "ref = " << ref << endl;
cout << "ref = " << ref << endl;
system("pause");
return 0;
}
结果:
ref = 10
ref = 10
ref = 2000
ref = 2000
2.5 引用的本质本质:引用的本质在c++内部实现是一个指针常量
#include
#include
using namespace std;
// 内部发现参数是引用,自动转换为 int* const x = &a;
void test(int& x) {
x = 100; // ref是引用,自动转换为*x = 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;
test(a);
system("pause");
return 0;
}
总结:c++推荐用引用,因为引用的本质是指针常量,但比使用指针方便,而且所有的指针 *** 作编译器都自动在内部转换做了。
2.6 常量引用作用:若不想在函数内部改变传入实参的值,用常量引用来做函数参数,防止函数内部误 *** 作。
语法:const 数据类型 形参名
在函数形参列表中,可以加const修饰形参,防止形参改变实参。
示例:
#include
#include
using namespace std;
// 形参用const修饰,防止内部误 *** 作改了实参
void showValue(const int& x) {
// x = 100; // 错误,在函数内部不可以修改实参的值
cout << "showValue :" << x << endl;
}
int main()
{
int a = 10;
showValue(a);
cout << "a = " << a << endl;
// int& ref = 10; // 引用本身需要一个合法的内存空间,不能直接给引用赋值数值
// 用const修饰就可以,原因:编译器优化代码为:int tmp =10; const int& ref = tmp;
// ref是tmp的别名,只是tmp这个原名我们不知道不可以使用。
const int& ref = 10;
system("pause");
return 0;
}
3 函数高级
3.1 函数默认参数
作用:函数的形参列表中形参可以有默认值,在函数调用时可传入实参或不传入。
语法:返回值类型 函数名(形参 = 默认值){}
注意事项:
- 如果参数列表某个位置有默认值,则从这个位置后的参数都必须有默认值
- 函数声明和函数实现只能有一个地方有默认值,不能同时出现默认值。
示例:
#include
#include
using namespace std;
int func(int a, int b, int c) {
return a + b + c;
}
// 正确案例
int func2(int a, int b=20, int c=30) {
return a + b + c;
}
// 错误案例
// 参数有默认值后,从这个位置后的参数都必须有默认值
//int func3(int a, int b = 20, int c) {
// return a + b + c;
//}
// 错误案例 声明和实现都有默认值
//int func4(int a, int b = 20, int c = 30);
//int func4(int a, int b = 20, int c = 30) {
// return a + b + c;
//}
int main()
{
cout << func(10, 20, 30) << endl;
cout << func2(10) << endl;
system("pause");
return 0;
}
3.2 函数占位参数
函数列表中可以有占位参数,用来做占位,调用函数时比如传入该位置的参数。
语法:返回值类型 函数名(数据类型){ }
示例:
#include
#include
using namespace std;
void func(int a, int) {
cout << "print func";
}
// 占位参数也可以给默认值
int func2(int a, int = 20) {
cout << "print func2";
}
int main()
{
// func(10); // 错误,必须传入占位参数位置的参数值
func(10, 20);
func2(10);
system("pause");
return 0;
}
3.3 函数重载
3.3.1 函数重载概述
作用:函数名可以相同,提高复用性。
函数重载的条件:
- 在同一个作用域下;
- 函数名相同;
- 函数参数类型不同,或参数个数不同,或参数顺序不同。
注意:函数返回值不同不能函数重载。
示例:
#include
#include
using namespace std;
void func() {
cout << "print func" << endl;
}
void func(int a) {
cout << "print func(int a)" << endl;
}
void func(double a) {
cout << "print func(double a)" << endl;
}
void func(int a,double b) {
cout << "print func (int a,double b)" << endl;
}
void func(double a, int b) {
cout << "print func (double a, int b)" << endl;
}
// 函数返回值不同不能函数重载 会报错
//int func(double a, int b) {
// cout << "print func (double a, int b)" << endl;
// return 0;
//}
int main()
{
func();
func(1);
func(1.23);
func(1,1.2);
func(1.2,1);
system("pause");
return 0;
}
3.3.2 函数重载注意事项
注意两点:
- 引用参数作为重载条件。有const和无const引用参数仍然可以重载,调用方式不同。
- 函数重载碰到默认参数,容易出错,尽量避免重载时使用默认参数。
示例:
#include
#include
using namespace std;
// 1 引用作为重载条件
void func(int& a) {
cout << "print func(int& a)" << endl;
}
void func(const int& a) {
cout << "print func(const int& a)" << endl;
}
// 2 默认参数构成重载条件
void func2(int a, int b = 10) {
cout << "print func2(int a,int b)" << endl;
}
void func2(int a) {
cout << "print func2(int a)" << endl;
}
int main()
{
int a = 10;
func(a);// 调用无const
func(1);// 调用有const
//func2(1);// 碰到默认参数产生歧义不知道调哪个函数,需要避免
func2(1, 10);
system("pause");
return 0;
}
4 类和对象
面向对象三大特性:
- 封装
- 继承
- 多态
c++认为万事万物皆为对象,每个对象都有属性和行为。
例如:
人可以作为对象,属性有姓名、年龄、身高、体重……,行为有走、跑、吃饭、睡觉……
车可以作为对象,属性有方向盘、轮胎、座椅……,行为有载人、放音乐、开空调……
具有相同性质的对象,可以抽象称为类,人属于人类,车属于车类。
4.1 封装 4.1.1 封装的意义封装的意义:
- 将属性和行为作为一个整体,表现事物
- 将属性和行为加以权限控制
意义1:将属性和行为作为一个整体,表现事物
语法:class 类名{
访问权限:
属性
行为
}
扩展:
- 类中的属性和行为,统一称为 成员
- 属性,又可称为成员属性,或成员变量
- 行为,又可称为成员函数,或成员方法
示例:
#include
#include
using namespace std;
const double PI = 3.14;
class Circle {
// 访问权限
public:
// 属性:
// 半径
int m_r;
// 行为:
// 圆的周长
double getZC() {
return 2 * PI * m_r;
}
};
int main()
{
// 通过圆类 创建具体的圆对象
// 实例化(通过一个类创建一个对象的过程)
Circle c1;
// 给圆对象的属性赋值
c1.m_r = 10;
// 获取对象的行为
cout << c1.getZC() << endl;
system("pause");
return 0;
}
意义2:将属性和行为加以权限控制
访问权限有三种:
| 权限名 | 作用域 |
| public 公共权限 | 类内可以访问,类外可以访问 |
| protected 保护权限 | 类内可以访问,类外不可以访问。子类可以访问父类成员 |
| private 私有权限 | 类内可以访问 类外不可以访问。子类不可以访问父类成员 |
示例:
#include
#include
using namespace std;
const double PI = 3.14;
class Person {
// 访问权限
public:
// 属性:
// 姓名
string m_name;
protected:
// 汽车
string m_car;
private:
// yhk密码
int m_password;
// 行为:
private:
double func() {
m_name = "张三";
m_car = "奔驰";
m_password = 1234567;
}
};
int main()
{
Person p;
p.m_name = "echo";
//p.m_car = "宝马";//不能访问
//p.m_password = 123;//不能访问
system("pause");
return 0;
}
4.1.2 struct和class区别
struct和class唯一的区别在于默认访问权限不同。
区别:
- struct 默认权限:公共
- class 默认权限:私有
示例:
#include
#include
using namespace std;
const double PI = 3.14;
class Test1 {
int a;// 默认权限私有
};
struct Test2 {
int a;// 默认权限公共
};
int main()
{
Test1 t1;
// t1.a = 10;// 报错
Test2 t2;
t2.a = 10;
system("pause");
return 0;
}
4.1.3 成员属性设置为私有
优点:
- 将所有成员属性私有化,自己控制读写权限;
- 对于写权限,可以检测数据写入的有效性。
示例:
#include
#include
using namespace std;
const double PI = 3.14;
class Men {
public:
void setName(string name) {
m_name = name;
}
string getName() {
return m_name;
}
void setAge(int age) {
if (age < 0 || age>150) {
cout << "你输入的年龄错误" << endl;
m_age = 0;
}
else {
m_age = age;
}
}
int getAge() {
return m_age;
}
void setLover(string lover) {
m_lover = lover;
}
private:
string m_name;
int m_age;
string m_lover;
};
int main()
{
Men m;
m.setName("张三");
cout << m.getName() << endl;
m.setAge(1000);
cout << m.getAge() << endl;
m.setLover("rose");
//cout << m.getLover() << endl; // 没有获取lover的函数
//cout << m.m_lover << endl; // 没有获取lover的权限
system("pause");
return 0;
}
4.2 对象的初始化和清理
4.2.1 构造函数和析构函数
对象的初始化和清理是两个非常重要的安全问题:
- 一个对象或变量没有初始化,对其使用的后果是未知的;
- 使用完一个对象或变量,没有及时清理,也可能会造成安全问题。
c++利用构造函数和析构函数解决上述问题。
这两个函数在类使用过程中会被编译器自动调用,完成对象初始化和清理工作。
如果代码不提供构造和析构,编译器会提供默认的空的构造函数和析构函数并调用。
构造函数:主要用于创建对象时为成员属性赋值,构造函数由编译器自动调用,无须手动调用。
析构函数:主要用于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名(){ }
特点:
- 没有返回值,也不用写void;
- 函数名与类名相同;
- 构造函数也可以有参数,可以有重载;
- 程序在调用对象时,一定会自动调用调用一次构造;
析构函数语法:~类名(){ }
特点:
- 没有返回值,也不用写void;
- 函数名与类名相同,在函数名前有一个~
- 析构函数不可以有参数,不能重载;
- 程序在对象销毁前会,一定会自动调用一次析构函数
示例:
#include
#include
using namespace std;
class Person {
public:
Person() {
cout << "这是构造函数" << endl;
}
~Person() {
cout << "这是析构函数" << endl;
}
};
void test() {
Person p;
}
int main()
{
test();
system("pause");
return 0;
}
结果:
这是构造函数
这是析构函数
4.2.2 构造函数的分类和调用
示例:
#include
#include
using namespace std;
class Person {
public:
// 无参构造 也是普通构造
Person() {
cout << "这是构造函数 无参" << endl;
}
// 有参构造 也是普通构造
Person(int a) {
cout << "这是构造函数 有参" << endl;
}
// 拷贝构造
Person(const Person& p) {
cout << "这是构造函数 拷贝" << endl;
name = p.name;
}
~Person() {
cout << "这是析构函数" << endl;
}
string name;
};
//调用法
// 1 括号法
void test() {
Person p1;
Person p2(10);
Person p3(p2);
// 注意事项 调用默认构造函数时,不要加括号
// 因为编译器会认为是一个函数声明,不会认为在创建对象
// Person p4();//无法达到创建对象效果
}
// 2 显示法
void test2() {
Person p21;
Person p22 = Person(10);
Person p23 = Person(p22);
// Person(10);//是匿名对象 特点:当前结束后,系统会立即回收匿名对象 在当前c++14及后续版本不支持该种写法了
// cout << "aaaa" << endl;
// 注意事项:不要利用拷贝构造函数初始化匿名对象,编译器会认为Person(p23) ===Person p23 对象声明
// Person(p23);//编译报错
}
// 3 隐式转换法
void test3() {
Person p31 = 10;// 相当于写了 Person p31=Person(10); 有参构造
Person p32 = p31;// 拷贝构造
}
int main()
{
//test();
//test2();
test3();
system("pause");
return 0;
}
4.2.3 拷贝构造函数调用时机
4.2.4 构造函数调用规则
4.2.5 深拷贝和浅拷贝
4.2.6 初始化列表
4.2.7 类对象作为类成员
4.2.8 静态成员
4.3 对象模型和this指针
4.4 友元
4.5 运算符重载
4.6 继承
4.7 多态
5 c++文件 *** 作
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)