文章目录
C++核心高级编程
1.内存分区模型
4个区域:
-
代码区:存放函数的二进制代码,由操作系统进行管理。
-
全局区:存放全局变量和静态变量以及常量。
-
栈区:由编译器自动分配释放,存放函数参数值,局部变量等。
-
堆区:由程序员分配和释放,程序员不释放的话,将由程序结束时操作系统回收。
意义:不同区域存放的数据,赋予不同生命周期,给程序员更大的空间去进行编程。
1.1 程序运行前
两个区域:
-
代码区:
存放CPU执行的机器指令。
代码区是共享的,目的是为了对频繁被执行的程序,在内存中只需有一份代码即可。
代码区是只读的,目的是为了防止程序被意外修改了它的指令。
-
全局区:
全局变量和静态变量,还包含了常量区,字符串常量和其他常量也存放在全局区。
全局区的数据在程序结束后由操作系统进行释放。
#include <iostream>
using namespace std;
//全局变量
int g_a = 1;
int g_b = 1;
//const修饰全局常量
const int c_g_a = 1;
const int c_g_b = 1;
int main()
{
//c - const g - global l - local
//局部变量
int a = 1;
int b = 1;
//静态变量
static int s_a = 1;
static int s_b = 1;
//局部常量
const int c_l_a = 1;
const int c_l_b = 1;
cout << "局部变量a的地址为:" << &a << endl;
cout << "局部变量b的地址为:" << &a << endl;
cout << "全局变量g_a的地址为" << &g_a << endl;
cout << "全局变量g_b的地址为" << &g_b << endl;
cout << "静态变量s_a的地址为:" << &s_a << endl;
cout << "静态变量s_b的地址为:" << &s_b << endl;
cout << "字符串常量的地址为:" << &"Hello" << endl;
cout << "const修饰的全局常量c_g_a的地址为:" << &c_g_a << endl;
cout << "const修饰的全局常量c_g_a的地址为:" << &c_g_b << endl;
cout << "局部常量c_l_a的地址为:" << &c_l_a << endl;
cout << "局部常量c_l_b的地址为:" << &c_l_b << endl;
system("pause");
return 0;
}
1.2 程序运行后
-
栈区:
由编译器自动分配释放,存放函数参数值,局部变量等。
不要返回局部变量的地址,因为栈区开辟的数据由编译器自动释放掉。
#include <iostream>
using namespace std;
int* fun(int b)
{
b = 2;
int a = 1;
return &a;
}
int main()
{
int* p = fun(10);
cout << "fun函数返回的值为:" << *p << endl; //第一次打印正确的数字1,因为编译器做了保留。
cout << "fun函数返回的值为:" << *p << endl; //第二次打印随机数
system("pause");
return 0;
}
-
堆区:
由程序员分配释放,如果程序员不释放,程序结束时由操作系统进行回收。
使用new在堆区进行开辟内存空间。
#include <iostream>
using namespace std;
int* fun()
{
int *p = new int(1); //指针也是局部变量,存放在栈区,但指针保存的数据存放在堆区
return p;
}
int main()
{
int* p = fun();
cout << "指针p指向的内容为:" << *p << endl;
cout << "指针p指向的内容为:" << *p << endl;
cout << "指针p指向的内容为:" << *p << endl;
system("pause");
return 0;
}
1.3 new操作符
作用:在堆区开辟数据。
语法结构:
-
开辟:new 数据类型
-
释放:delete 数据类型
使用new创建的数据,会返回该数据对于的类型指针。在堆区开辟的空间,需要程序员手动释放,释放使用操作符delete。
#include <iostream>
using namespace std;
int* fun()
{
int *p = new int(1);
return p;
}
void fun2()
{
//在堆区开辟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] << " ";
}
cout << endl;
delete[] arr;
}
int main()
{
int* p = fun();
fun2();
cout << *p << endl;
cout << *p << endl;
cout << *p << endl;
delete p;
return 0;
}
2.引用
2.1 使用
作用:给变量起别名。
语法结构:数据类型 &别名 = 原名
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int& b = a;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
b = 20;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
system("pause");
return 0;
}
2.2 注意事项
-
引用必须初始化
-
在初始化后不可修改
#include <iostream>
using namespace std;
int main()
{
int x = 10;
int y = 20;
//int& z; //报错
int& z = x;
z = y; //此处是赋值操作,而不是更改引用!
cout << "x = " << x << endl; //20
cout << "y = " << y << endl; //20
cout << "z = " << z << endl; //20
system("pause");
return 0;
}
2.3 做函数参数
作用:函数传参的时候,使用引用的技术让形参修饰实参。可简化指针修改实参。
#include <iostream>
using namespace std;
void swap(int &a , int &b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int a = 10;
int b = 20;
swap(a, b);
cout << "a = " << a << endl; //20
cout << "b = " << b << endl; //10
system("pause");
return 0;
}
2.4 做函数返回值
作用:作为函数返回值所存在。
#include <iostream>
using namespace std;
int& test1()
{
int a = 10;
return a;
}
int& test2()
{
static int a = 10;
return a;
}
int main()
{
int& ret1 = test1();
cout << "ret1 = " << ret1 << endl;
cout << "ret1 = " << ret1 << endl;
int& ret2 = test2();
cout << "ret2 = " << ret2 << endl;
cout << "ret2 = " << ret2 << endl;
test2() = 20;
cout << "ret2 = " << ret2 << endl;
cout << "ret2 = " << ret2 << endl;
system("pause");
return 0;
}
补充:不要返回局部变量引用。
2.5 本质
在C++内部实现是一个指针常量。
#include <iostream>
using namespace std;
void fun(int& ret) //转换为:int* const ret = &a;
{
ret = 30; //转换为:*ret = 30;
}
int main()
{
int a = 10;
int& ret = a; //自动转换为:int* const ret = &a;指针常量的指针指向不可改
ret = 20;
cout << "a = " << a << endl;
cout << "ret = " << ret << endl;
fun(a);
system("pause");
return 0;
}
2.6 常量引用
作用:修饰形参,防止误操作。
函数形参列表中,可加const修饰形参,以便防止形参改变实参。
#include <iostream>
using namespace std;
void Show1(int& x)
{
x = 20;
cout << x << endl; //20
}
void Show2(const int& x)
{
x = 20; //报错
cout << x << endl;
}
int main()
{
//int& ref = 10; //报错,引用本身需一个合法的内存空间
//const int& ref = 10; //加入const,编译器会将代码修改成 int temp = 10;const int& ref = temp;
//ref = 20; //报错,加了const变得只读,不可修改
int a = 10;
Show1(a);
cout << a << endl; //20
system("pause");
return 0;
}
3.函数提高
3.1 函数默认参数
语法结构:返回值类型 函数名 (参数 = 默认值){}
#include <iostream>
using namespace std;
int func1(int a = 1, int b = 2, int c = 3)
{
return a + b + c;
}
int func2(int a = 1, int b = 2);
int func2(int a , int b)
{
return a + b;
}
int main()
{
int num1 = func1();
int num2 = func2();
cout << num1 << endl; //6
cout << num2 << endl; //3
system("pause");
return 0;
}
补充:
-
如果某位置参数有默认值,那该位置往后,从左向右,都需要有默认值。
-
如果函数声明有默认值,函数实现的时候就不能有默认参数。
3.2 函数占位参数
语法结构:返回值类型 函数名
#include <iostream>
using namespace std;
void func(int a , int) //占位参数也可有默认参数
{
cout << "func函数的调用" << endl;
}
int main()
{
func(10, 20); //占位参数必须填补
system("pause");
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(int a, double b)
{
cout << "func(int a, double b)函数的调用" << endl;
}
int main()
{
func(); //func函数的调用
func(10); //func(int a)函数的调用
func(10, 3.14); //func(int a, double b)函数的调用
system("pause");
return 0;
}
补充:函数返回值不可作函数重载条件。
3.3.2 注意事项
-
将引用作为重载条件
-
函数重载遇到函数默认参数
//将引用作为重载条件
#include <iostream>
using namespace std;
void func(int& a)
{
cout << "func(int& a)函数的调用" << endl;
}
void func(const int& a)
{
cout << "func(const int& a)函数的调用" << endl;
}
int main()
{
int a = 10;
func(a); //func(int& a)函数的调用
func(10); //func(const int& a)函数的调用
system("pause");
return 0;
}
//函数重载遇到函数默认参数
#include <iostream>
using namespace std;
void func(int a)
{
cout << "func(int a)函数的调用" << endl;
}
void func(int a, int b =10)
{
cout << "func(int a, int b =10)函数的调用" << endl;
}
int main()
{
int a = 10;
func(10); //报错,出现二义性。
system("pause");
return 0;
}
4.类和对象
C++面对对象的三大特征:封装、继承、多态
4.1 封装
4.1.1 封装的意义
-
将属性和行为作为一个整体,常用来表现生活中的事物
-
将属性和行为加以权限以便进行控制
语法结构:class 类名 { 访问权限: 属性 / 行为 };
#include <iostream>
using namespace std;
const int PI = 3.14;
class Circle
{
//访问权限
//public - 公共权限
public:
//属性
//半径
int m_r;
//行为
//计算圆的面积
double calculateCircleArea()
{
return PI * (m_r * m_r);
}
};
int main()
{
//创建一个具体的圆(对象)
Circle c1;
//对圆(对象)的属性进行赋值操作
c1.m_r = 3;
cout << "半径为" << c1.m_r << "的面积为" << c1.calculateCircleArea() << endl;
system("pause");
return 0;
}
#include <iostream>
#include <string>
using namespace std;
class Student
{
public:
void getID(int id)
{
m_id = id;
}
void getName(string name)
{
m_name = name;
}
void getAge(int age)
{
m_age = age;
}
void showStudent()
{
cout << m_name << "学号是" << m_id << "年龄是" << m_age << endl;
}
public:
int m_id;
string m_name;
int m_age;
};
int main()
{
Student s1;
s1.getID(123456);
s1.getName("小明");
s1.getAge(18);
s1.showStudent();
system("pause");
return 0;
}
三种访问权限:
-
public 公共权限
-
protected 保护权限
-
private 私有权限
#include <iostream>
using namespace std;
// public - 类内可访问,类外可访问
// protected - 类内可访问,类外不可访问
// private - 类内可访问,类外不可访问
class Person
{
public:
string m_Name;
protected:
string m_Phone;
private:
string m_Password;
public:
void func()
{
m_Name = "小红";
m_Phone = "HW";
m_Password = "ABC123";
}
};
int main()
{
Person p1;
p1.m_Name = "小明";
p1.m_Phone = "HW"; //报错,protected类外不可访问
p1.m_Password = "abc123"; //报错,private类外不可访问
system("pause");
return 0;
}
4.1.2 struct与class的区别
默认访问权限不同:
-
struct默认权限为公共
-
class 默认权限为私有
#include <iostream>
#include <string>
using namespace std;
struct Person1
{
string m_Name;
};
class Person2
{
string m_Name;
};
int main()
{
Person1 p1;
p1.m_Name = "小明";
Person2 p2;
p2.m_Name = "小红"; //报错
system("pause");
return 0;
}
4.2 对象的初始化和清理
4.2.1 构造函数和析构函数
对象的初始化和清理是两个重要的安全问题:
-
一个对象或变量没有初始状态,对于其使用后的结果是未知的。
-
使用完一个对象或变量,没及时清理会造成一定的安全问题。
在C++中使用构造函数和析构函数来解决以上问题,如果我们不提供构造和析构函数,编译器会提供构造函数和析构函数(空实现)。
-
构造函数:作用于创建对象时对对象的成员属性进行赋值,构造函数有编译器自动调用,无需手动调用。
-
析构函数:作用于在对象销毁前系统自动调用,进行清理工作。
构造函数语法结构:类名(){}
-
无返回值,无需写void
-
函数名和类型相同
-
可以有参数,因此可发生重载
-
程序在调用对象时会自动调用,无序手动调用,指挥调用一次
析构函数语法结构:~类名(){}
-
无返回值,无需写void
-
函数名于类名相同,在名称前加上符号~
-
不可有参数,由此不可发生重载
-
程序在对象销毁前会自动调用,无序手动调用,只会调用一次。
#include <iostream>
using namespace std;
class Student
{
public:
Student()
{
cout << "Student的构造函数调用" << endl;
}
~Student()
{
cout << "Student的析构函数调用" << endl;
}
};
void test()
{
Student s;
}
int main()
{
test();
system("pause");
return 0;
}
4.2.2 构造函数的分类及调用
两种分类方式:
-
参数分为:有参构造和无参构造
-
类型分为:普通构造和拷贝构造
三种调用方式:
-
括号法
-
显示法
-
隐式转换法
#include <iostream>
using namespace std;
class Student
{
public:
Student()
{
cout << "Student的默认构造函数调用" << endl;
}
Student(int a)
{
age = a;
cout << "Student的有参构造函数调用" << endl;
}
Student(const Student& s)
{
age = s.age;
cout << "Student的拷贝构造函数调用" << endl;
}
~Student()
{
cout << "Student的析构函数调用" << endl;
}
int age;
};
void test()
{
//1.括号法
//Student s1(); //调用默认构造函数时,不要加(),编译器会认为是一个函数的声明
Student s1; //默认构造函数调用
Student s2(18); //有参构造函数调用
Student s3(s2); //拷贝构造函数调用
cout << "s2的年龄是" << s2.age << endl;
cout << "s3的年龄是" << s3.age << endl;
//2.显示法
Student s4;
Student s5 = Student(19);
Student s6 = Student(s5);
//Student(20); //匿名对象,当前行执行结束后,系统自动回收掉。
//Student(s6); //不要用拷贝构造函数,初始化匿名对象,编译器会认为Student(s5)= Student s5;
//
//3.隐式转换法
Student s7 = 17; //相当于Student s7 = Student(17)
Student s8 = s7;
}
int main()
{
test();
system("pause");
return 0;
}
4.2.3 拷贝构造函数调用时机
三种情况:
-
使用一个已经创建完毕的对象来初始化一个新的对象
-
值传递的方式给函数参数传值
-
以值方式返回局部对象
#include <iostream>
using namespace std;
class Student
{
public:
Student()
{
cout << "Student的默认构造函数调用" << endl;
}
Student(int age)
{
cout << "Student的有参构造函数调用" << endl;
m_age = age;
}
Student(const Student& s)
{
cout << "Student的拷贝构造函数调用" << endl;
m_age = s.m_age;
}
~Student()
{
cout << "Student的析构构造函数调用" << endl;
}
int m_age;
};
//使用一个已经创建完毕的对象来初始化一个新的对象
void test01()
{
Student s1(18);
Student s2(s1);
cout << "s2的年龄是" << s2.m_age << endl;
}
void Work1( Student s)
{
}
//值传递的方式给函数参数传值
void test02()
{
Student s3;
Work1(s3);
}
Student work2()
{
Student s5;
cout << &s5 << endl;
return s5;
}
//以值方式返回局部对象
void test03()
{
Student s4 = work2();
cout << &s4 << endl;
}
int main()
{
test01();
test02();
test03();
system("pause");
return 0;
}
4.2.4 构造函数调用规则
默认情况下,C++编译器中至少会给一个类添加三个函数:
-
默认构造函数(无参,函数体为空)
-
默认拷贝构造函数,对属性进行值拷贝
-
默认析构函数(无参,函数体为空)
调用规则:
-
如果用户定义了有参构造函数,C++将不会提供默认无参构造函数,但会提供默认拷贝构造函数。
-
如果用户定义了拷贝构造函数,C++将不会提供其他的构造函数。
//如果用户定义了有参构造函数,C++将不会提供默认无参构造函数,但会提供默认拷贝构造函数。
#include <iostream>
using namespace std;
class Student
{
public:
Student()
{
cout << "Student的默认构造函数调用" << endl;
}
Student(int age)
{
cout << "Student的有参构造函数调用" << endl;
m_age = age;
}
/*Student(const Student& s)
{
cout << "Student的拷贝构造函数调用" << endl;
m_age = s.m_age;
}*/
~Student()
{
cout << "Student的析构函数调用" << endl;
}
int m_age;
};
void test01()
{
Student s1;
s1.m_age = 18;
Student s2(s1);
cout << "s2的年龄是:" << s2.m_age << endl;
}
//void test02()
//{
// Student s3(20);
// Student s4(s3);
// cout << "s4的年龄是" << s4.m_age << endl;
//}
int main()
{
test01();
//test02();
system("pause");
return 0;
}
4.2.5 深拷贝与浅拷贝
浅拷贝:赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
#include <iostream>
using namespace std;
class Student
{
public:
Student()
{
cout << "Student的默认构造函数调用" << endl;
}
Student(int age, int weight)
{
m_age = age;
m_weight = new int(weight);
cout << "Student的有参构造函数调用" << endl;
}
Student(const Student& s)
{
cout << "Student的拷贝构造函数调用" << endl;
m_age = s.m_age;
m_weight = new int(*s.m_weight);
}
~Student()
{
if (m_weight != NULL)
{
delete m_weight;
m_weight = NULL;
}
cout << "Student的析构函数调用" << endl;
}
int m_age;
int* m_weight;
};
void test01()
{
Student s1(18, 120);
cout << "s1的年龄是" << s1.m_age << ",体重是:" << *s1.m_weight << endl;
Student s2(s1);
cout << "s2的年龄是" << s2.m_age << ",体重是:" << *s2.m_weight << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
4.2.6 初始化列表
作用:初始化属性
语法结构:构造函数():属性1(值1), 属性2(值2)…{}
#include <iostream>
using namespace std;
class Student
{
public:
Student():m_age(18),m_height(180)
{
}
Student(int age, int height) :m_age(age), m_height(height)
{
}
int m_age;
int m_height;
};
void test01()
{
Student s1;
cout << "s1的年龄是" << s1.m_age << endl;
cout << "s1的身高是" << s1.m_height << endl;
Student s2(20,175);
cout << "s2的年龄是" << s2.m_age << endl;
cout << "s2的身高是" << s2.m_height << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
4.2.7 类对象作为类成员
C++的类中成员可以是另一个类的对象,称该成员为对象成员。
#include <iostream>
#include <string>
using namespace std;
class Phone
{
public:
Phone(string pName)
{
m_PName = pName;
cout << "Phone的构造函数调用" << endl;
}
~Phone()
{
cout << "Phone的析构函数调用" << endl;
}
string m_PName;
};
class Student
{
public:
Student(string name, string pName):m_Name(name), m_Phone(pName)
{
/* m_Name = name;
m_Phone = pName;*/
cout << "Student的构造函数调用" << endl;
}
~Student()
{
cout << "Student的析构函数调用" << endl;
}
string m_Name;
Phone m_Phone;
};
void test01()
{
Student s1("小明", "HW");
cout << s1.m_Name << "使用" << s1.m_Phone.m_PName << "手机" << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
总结:当其他类对象作为本类成员时,构造时先构造类对象,再构造自身,析构的顺序与构造相反。
4.2.8 静态成员
在成员变量和成员函数前加上关键字static,称为静态成员。
分类:
-
静态成员变量
所有对象共享同一份数据 在编译阶段时分配内存 类内声明,类外初始化
-
静态成员函数所有对象共享同个函数静态成员函数只能访问静态成员变量
静态成员变量:
#include <iostream>
using namespace std;
class Student
{
public:
static int m_A;
private:
static int m_B;
};
int Student::m_A = 10;
int Student::m_B = 20;
void test01()
{
//静态成员变量两种访问方式
//通过对象
Student s1;
s1.m_A = 100;
cout << "s1的m_A值为:" << s1.m_A << endl;
Student s2;
s2.m_A = 200;
cout << "s1的m_A值为:" << s1.m_A << endl; //共享同一份数据
cout << "s2的m_A值为:" << s2.m_A << endl;
//通过类名
cout << "s1的m_A值为:" << Student::m_A << endl;
cout << "s2的m_A值为:" << Student::m_A << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
静态成员函数:
#include <iostream>
using namespace std;
class Student
{
public:
static void func()
{
m_A = 10; //静态成员函数可访问静态成员变量
//m_B = 20; //报错,静态成员函数不可访问非静态成员变量
cout << "statc void func函数的调用" << endl;
}
private:
static void func2()
{
m_A = 10;
//m_B = 20;
cout << "statc void func2函数的调用" << endl;
}
public:
static int m_A;
int m_B;
};
int Student::m_A = 10;
//int Student::m_B = 20; //报错,非静态成员变量不可在类外定义
void test01()
{
//通过对象访问
Student s1;
s1.func();