Java 转 C++ 系列:核心编程(一)
文章参考:黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难
文章目录
一、内存分区
C++程序在执行时,将内存大方向划分为 4个区域:
- 代码区:存放函数体的二进制代码,由操作系统进行管理
- 全局区:存放 全局变量、静态变量、全局常量、字符串常量。
常量区属于全局区,其存放 全局常量、字符串常量 - 栈区:由编译器自动分配释放,存放函数的参数值,局部变量、局部常量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
#include<iostream>
#include<string>
using namespace std;
//全局变量
int g_a = 10;// 全局区
int g_b = 10;
//全局常量
const int c_g_a = 10; // 常量区, 也是全局区
const int c_g_b = 10;
int main() {
system("chcp 65001> nul");
//静态变量
static int s_a = 10; // 全局区
static int s_b = 10;
cout << "字符串常量地址为: " << (int)&"hello world" << endl; // 常量区, 也是全局区
cout << "字符串常量地址为: " << (int)&"hello world1" << endl;
//局部变量
int a = 10; // 栈区
int b = 10;
// 局部常量
const int c_l_a = 10;// 栈区
const int c_l_b = 10;
cout << "---------------------------------" << endl;
// 栈区:由编译器自动分配释放, 存放函数的参数值, 局部变量等
// 注:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
// 堆区:由程序员分配释放, 若程序员不释放, 程序结束时由操作系统回收
// 注:在C++中主要利用new在堆区开辟内存,用完后利用delete释放内存
int* arr = new int[10];
for (int i = 0; i < 10; i++) {
arr[i] = i;
}
delete[] arr;//释放数组 delete 后加 []
//cout << arr[0] << endl;// 报错:访问了已经释放的内存
// 如果没有delete,程序运行期间,会发生内存泄漏,从而引发系统崩溃
system("pause");
return 0;
}
注:在程序编译后,生成了exe可执行程序,未执行该程序前分为 全局区、代码区 两个区域。
- 代码区特点是共享和只读
- 全局区的数据在程序结束后由操作系统释放
二、引用
2.1 引用定义和基本使用
引用:对变量的起一个别名,引用必须初始化,并且一旦初始化后就不能改变引用的对象。引用的语法更清楚简单,通过引用参数产生的效果同按地址传递是一样的 。
#include<iostream>
#include<string>
using namespace std;
// 函数声明
void swap01(int a, int b);
void swap02(int* a, int* b);
void swap03(int& a, int& b);
int main() {
system("chcp 65001> nul");
int c = 10;
//int& d; // 错误:引用必须初始化
int& d = c; // d是c的引用,d和c指向同一个内存地址
//d = a;// 这是赋值操作,不是改变引用的对象,d仍然是c的引用,只不过c的值被修改了
cout << "c的地址为: " << (int)&c << endl;
cout << "d的地址为: " << (int)&d << endl; // 输出地址相同
c = 20; // 修改c的值,d的值也会改变,因为它们指向同一个内存地址
cout << "c的值为:" << c << endl;
cout << "d的值为:" << d << endl;
cout << "---------------------------------" << endl;
int a = 10; // 栈区
int b = 10;
swap01(a, b);
cout << "a:" << a << " b:" << b << endl; // 10,20
swap02(&a, &b);
cout << "a:" << a << " b:" << b << endl; // 交换成功 20,10
swap03(a, b);
cout << "a:" << a << " b:" << b << endl; // 交换成功 10, 20
system("pause");
return 0;
}
//1. 值传递
void swap01(int a, int b) {
int temp = a;
a = b;
b = temp;
}
//2. 地址传递
void swap02(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
//3. 引用传递
void swap03(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
2.2 引用可以做返回值
引用可以做函数返回值,但不要返回局部变量引用,可以返回静态变量的引用。
另外,常量引用主要用来修饰形参,防止误操作。
#include<iostream>
#include<string>
using namespace std;
int& test01();
int& test02();
int main() {
system("chcp 65001> nul");
//不能返回局部变量的引用
int& ref = test01();
//cout << "ref = " << ref << endl; // 编译器不会报错,但运行时可能会出现错误,因为ref引用了一个已经被释放的内存地址
// 可以返回静态变量的引用,因为静态变量存在全局区,不会被释放
int& ref2 = test02();
cout << "ref2 = " << ref2 << endl; // 20
// 如果函数的返回值是一个引用,那么函数调用可以作为左值使用
test02() = 1000; // 相当于 a = 1000, ref2又是a的引用,所以ref2的值也会改变
cout << "ref2 = " << ref2 << endl; // 1000
cout << "---------------------------------" << endl;
//常量引用
//常量引用主要用来修饰形参,防止误操作
//void showValue(const int& v) {
// //v += 10; // 错误:常量引用不能修改引用的对象
//}
system("pause");
return 0;
}
//返回局部变量引用
int& test01() {
int a = 10; //局部变量
return a;
}
//返回静态变量引用
int& test02() {
static int a = 20; //静态变量,存在全局区,不会被释放
return a;
}
2.3 引用的本质
引用的本质是指针常量,即指针指向不能修改,指针指向的内容可以修改。
int& ref = aa 相当于 int* const ref = &aa
#include<iostream>
#include<string>
using namespace std;
// 函数声明
void func(int& ref);
int main() {
system("chcp 65001> nul");
// 引用的本质:内部实现是一个指针常量
int aa = 10;
int& ref3 = aa; // 相当于 int* const ref3 = &aa,即指针常量,指针指向不能修改,指针指向的内容可以修改
ref3 = 20;
cout << aa << endl; // 20
func(aa);
cout << aa << endl; // 100
system("pause");
return 0;
}
void func(int& ref) { // int& ref = aa 相当于 int* const ref = &aa
ref = 100; // ref是引用,转换为*ref = 100
// 引用 = 自动取地址 + 自动解引用 + 地址不可修改的常量指针
}
三、函数提高
3.1 函数形参默认值与占位符
在C++中,函数的形参列表中的形参是可以有默认值的。
- 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
- 如果采用先声明再定义的方式,默认参数只写在「函数声明」
#include<iostream>
using namespace std;
// 1. 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
// 2. 默认参数只写在「函数声明」
int func(int a, int b = 10, int c = 10);
// 函数占位参数,占位参数也可以有默认参数,调用函数时必须填补该位置
void func2(int a, int);
int main() {
system("chcp 65001> nul");
cout << "ret = " << func(20, 20) << endl; // 20 + 20 + 10
cout << "ret = " << func(100) << endl; // 100 + 10 + 10
func2(10, 10); //占位参数必须填补
system("pause");
return 0;
}
// 定义
int func(int a, int b, int c) {
return a + b + c;
}
void func2(int a, int) {
cout << "this is func2" << endl;
}
3.2 函数重载
函数重载:函数名相同,参数列表不同(个数不同,类型不同,顺序不同),和形参变量的名称以及返回值的类型无关。
#include<iostream>
#include<string>
using namespace std;
// 函数重载:函数名相同,参数列表不同(个数不同,类型不同,顺序不同),和形参变量的名称无关。
// 函数重载需要函数都在同一个作用域下
void func3();
void func3(int a);
void func3(double a);
void func3(int a, double b);
void func3(double a, int b);
int main() {
system("chcp 65001> nul");
// 函数重载
func3();
func3(10);
func3(3.14);
func3(10, 3.14);
func3(3.14, 10);
system("pause");
return 0;
}
// 重载
void func3() {
cout << "func3 的调用!" << endl;
}
void func3(int a) {
cout << "func3 (int a) 的调用!" << endl;
}
void func3(double a) {
cout << "func3 (double a)的调用!" << endl;
}
void func3(int a, double b) {
cout << "func3 (int a ,double b) 的调用!" << endl;
}
void func3(double a, int b) {
cout << "func3 (double a ,int b)的调用!" << endl;
}
注:函数重载需要函数都在同一个作用域下
3.3 引用作为重载条件,默认参数下的歧义
#include<iostream>
#include<string>
using namespace std;
// 引用作为重载条件
void func4(int& a);
void func4(const int& a);
// 重载遇到默认参数会产生歧义
void func5(int a, int b = 10);
void func5(int a);
int main() {
system("chcp 65001> nul");
// 引用作为重载条件
int a = 10;
func4(a); // func4 (int &a) 调用
func4(10); // func4 (const int &a) 调用
//func5(10); // 报错:调用 func5(int a) 还是 func5(int a, int b = 10) 都可以,产生歧义
//int b = 10;
//func5(b); // 报错,原因同上
func5(10, 20); // func5(int a, int b = 10) 调用
system("pause");
return 0;
}
// 引用作为重载条件
void func4(int& a) {
cout << "func4 (int &a) 调用 " << endl;
}
void func4(const int& a) {
cout << "func4 (const int &a) 调用 " << endl;
}
// 重载遇到默认参数会产生歧义
void func5(int a, int b) {
cout << "func5(int a, int b = 10) 调用" << endl;
}
void func5(int a) {
cout << "func5(int a) 调用" << endl;
}
四、类和对象
4.1 封装
C++面向对象的三大特性为:封装、继承、多态,封装作为C++面向对象三大特性之一,其意义如下:
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
| 权限关键字 | 本类内部 | 子类(派生类)内部 | 类外部(main / 普通函数) | 特点总结 |
|---|---|---|---|---|
| public | ✅ 可以 | ✅ 可以 | ✅ 可以 | 完全公开,谁都能访问 |
| protected | ✅ 可以 | ✅ 可以 | ❌ 不可以 | 传子不传外,对外隐藏,对子类开放 |
| private | ✅ 可以 | ❌ 不可以 | ❌ 不可以 | 绝对私有,只有自己能访问 |
#include<iostream>
#include<string>
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() {
system("chcp 65001> nul");
Person p;
p.m_Name = "李四";
//p.m_Car = "奔驰"; //保护权限类外访问不到
//p.m_Password = 123; //私有权限类外访问不到
system("pause"); // 程序卡死在这里,等待按键,按键后继续执行,执行完后程序结束,析构函数调用
return 0;
}
注:
-
在C++中,struct和class唯一的区别就在于默认的访问权限不同。struct默认的访问权限是public,而class默认的访问权限是private。
-
通常设计成员为私有的,提供公共的成员函数来访问和修改私有成员,这样可以控制对数据的访问,保证数据的安全性和完整性
-
C++ 和 Java在私有成员访问上是不同的
C++ private:同类所有对象互相可见class Person { private: string name; // 私有成员 public: Person(string n) : name(n) {} // 拷贝构造:直接访问另一个对象的私有name Person(const Person& p) { this->name = p.name; // ✅ C++ 允许 } };Java private:只对自己可见,同类其他对象必须用 getter
class Person { private String name; public Person(String n) { name = n; } // getter public String getName() { return name; } // 拷贝构造 public Person(Person p) { // this.name = p.name; ❌ Java 报错! this.name = p.getName(); // ✅ Java 必须这样 } }
4.2 对象的初始化和清理
构造函数语法:类名(){}
- 构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法: ~类名(){}
- 析构函数,没有返回值也不写void
- 函数名称与类名相同,在名称前加上符号 ~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
#include<iostream>
#include<string>
using namespace std;
class Student {
public:
Student() {
cout << "Student构造函数调用" << endl;
}
~Student() {
cout << "Student析构函数调用" << endl;
}
};
int main() {
system("chcp 65001> nul");
Student s; // 构造函数调用
system("pause"); // 程序卡死在这里,等待按键,按键后继续执行,执行完后程序结束,析构函数调用
return 0;
}
4.3 构造函数的分类
两种分类方式:
- 按参数分为: 有参构造和无参构造
- 按类型分为: 普通构造和拷贝构造
三种调用方式:括号法、显示法、隐式转换法
#include<iostream>
#include<string>
using namespace std;
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int a) {
age = a;
cout << "有参构造函数!" << endl;
}
//拷贝构造函数
Person(const Person& p) { // 浅拷贝
age = p.age;
cout << "拷贝构造函数!" << endl;
}
//析构函数
~Person() {
cout << "析构函数!" << endl;
}
private:
int age;
};
int main() {
system("chcp 65001> nul");
// 1. 括号法,常用
Person p1; //无参
Person p2(10); //有参
Person p3(p1); //拷贝
//注意1:调用无参构造函数不能加括号
// 2. 显示法
Person p4 = Person();
Person p5 = Person(10);
Person p6 = Person(p4);
//注意2:Person()单独一行写就是匿名对象 当前行结束之后,马上析构
// 3. 隐式转换法
Person p7 = 10; // 有参 Person p4 = Person(10);
Person p8 = p7; // 拷贝 Person p5 = Person(p4);
cout << "---------------------------------" << endl;
//注意3:不能利用 拷贝构造函数 初始化匿名对象,编译器会认为是对象声明
//Person p9(Person()); // 报错
system("pause");
return 0;
}
4.4 拷贝构造函数调用时机
拷贝构造函数调用时机通常有三种情况:
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象(理论上会调用拷贝构造,但多数情况编译器会做优化)
// 情况1 参考上文
// 情况2
void doWork(Person p1) {...}
void test02() {
Person p;
doWork(p); //这里会调用拷贝构造函数
}
// 情况3
Person doWork2(){
Person p1;
cout << (int*)&p1 << endl;
return p1;
}
void test03() {
Person p = doWork2(); // 这里会调用拷贝构造函数(理论上会)
}
4.5 深浅拷贝
浅拷贝:简单的 赋值拷贝 操作
深拷贝:在堆区重新申请空间,进行拷贝操作
默认情况下,c++编译器至少给一个类添加 3 个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝,即浅拷贝
#include<iostream>
#include<string>
using namespace std;
class Animal {
public:
//无参(默认)构造函数
Animal() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Animal(int age, int height) {
cout << "有参构造函数!" << endl;
m_age = age;
m_height = new int(height);
}
//拷贝构造函数
Animal(const Animal& a) {
cout << "拷贝构造函数!" << endl;
//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
m_age = a.m_age;
m_height = new int(*a.m_height);
//如果用浅拷贝,a1和a2的m_height指针指向同一块内存,析构时重复释放 → 程序崩溃
}
//析构函数
~Animal() {
cout << "析构函数!" << endl;
if (m_height != NULL) {
delete m_height;
}
}
private:
int m_age;
int* m_height;
};
int main() {
system("chcp 65001> nul");
Animal a1(18, 180);
Animal a2(a1); // 调用深拷贝
system("pause");
return 0;
}
另外,构造函数调用规则如下:
- 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
4.6 初始化列表
C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)... {}
#include<iostream>
#include<string>
using namespace std;
class Person {
public:
//传统方式初始化
//Person(int a, int b, int c) {
// m_A = a;
// m_B = b;
// m_C = c;
//}
//初始化列表方式初始化
Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}
void PrintPerson() {
cout << "mA:" << m_A << endl;
cout << "mB:" << m_B << endl;
cout << "mC:" << m_C << endl;
}
private:
int m_A;
int m_B;
int m_C;
};
int main() {
system("chcp 65001> nul");
Person p(1, 2, 3);
p.PrintPerson();
system("pause");
return 0;
}
4.7 类对象作为类成员
当类中成员是其他类对象时,我们称该成员为 对象成员。
构造的顺序:先调用对象成员的构造,再调用本类构造。析构顺序与构造相反。
#include<iostream>
#include<string>
using namespace std;
class Phone {
public:
Phone(string name) {
m_PhoneName = name;
cout << "Phone构造" << endl;
}
~Phone() {
cout << "Phone析构" << endl;
}
string m_PhoneName;
};
class Person {
public:
//初始化列表可以告诉编译器调用哪一个构造函数
Person(string name, string pName) :m_Name(name), m_Phone(pName) {
cout << "Person构造" << endl;
}
~Person() {
cout << "Person析构" << endl;
}
void playGame() {
cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手机! " << endl;
}
string m_Name;
Phone m_Phone;
};
int main() {
system("chcp 65001> nul");
Person p("张三", "苹果X");
p.playGame();
// Phone构造
// Person构造
// 张三 使用苹果X 牌手机!
// Person析构
// Phone析构
system("pause");
return 0;
}
4.8 静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员,其分为:
- 静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
- 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
#include<iostream>
using namespace std;
class Person {
public:
static int m_A; //静态成员变量
static void func() {
cout << "静态func调用" << endl;
m_A = 100;
//m_B = 100; //错误,不可以访问非静态成员变量
}
private:
int m_B;
static int m_C; //静态成员变量也是有访问权限的
static void func2() {
cout << "func2调用" << endl;
}
};
int Person::m_A = 10; // 必须 类内声明,类外初始化
int main() {
system("chcp 65001> nul");
//静态成员变量和静态成员函数的两种访问方式
//1、通过对象
Person p1;
p1.m_A = 100;
p1.func();
cout << "p1.m_A = " << p1.m_A << endl;
Person p2;
p2.m_A = 200;
cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据
cout << "p2.m_A = " << p2.m_A << endl;
//2、通过类名
cout << "m_A = " << Person::m_A << endl;
Person::func();
//cout << "m_C = " << Person::m_C << endl; //虽说是static,但是私有权限访问不到
//Person::func2();
system("pause");
return 0;
}
五、C++对象模型和this指针
5.1 成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上。
#include<iostream>
#include<string>
using namespace std;
class Person {
public:
Person() {
mA = 0;
}
//非静态成员变量占对象空间
int mA;
//静态成员变量不占对象空间
static int mB;
//函数也不占对象空间,所有函数共享一个函数实例
void func() {
}
//静态成员函数也不占对象空间
static void sfunc() {
}
};
int main() {
system("chcp 65001> nul");
cout << sizeof(Person) << endl;
// 输出4,Person类中只有一个非静态成员变量mA,占4个字节,静态成员变量和函数不占对象空间
system("pause");
return 0;
}
5.2 this指针
this 指针是隐含每一个非静态成员函数内的一种指针,主要作用如下:
- 当形参和成员变量同名时,可用 this 指针来区分
- 在类的非静态成员函数中返回对象本身,可使用
return *this
#include<iostream>
#include<string>
using namespace std;
class Animal {
public:
Animal(int age) {
this->age = age;
}
int getAge() {
return age;
}
private:
int age;
};
int main() {
system("chcp 65001> nul");
Animal a(10);
cout << "a.age = " << a.getAge() << endl;
system("pause");
return 0;
}
5.3 空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到 this 指针,如果用到 this 指针,需要加以判断保证代码的健壮性。
//空指针访问成员函数
class Person {
public:
void ShowClassName() {
cout << "我是Person类!" << endl;
}
void ShowPerson() {
if (this == NULL) {
return;
}
cout << mAge << endl;
}
public:
int mAge;
};
int main() {
system("chcp 65001> nul");
Person * p = NULL;
p->ShowClassName(); //空指针,可以调用成员函数
p->ShowPerson(); //但是如果成员函数中用到了this指针,就不可以了
system("pause");
return 0;
}
5.4 const修饰成员函数
常函数:
- 成员函数后加 const 后我们称为这个函数为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字 mutable 后,在常函数中依然可以修改
常对象:
- 声明对象前加 const 称该对象为常对象
- 常对象只能调用常函数
#include<iostream>
using namespace std;
class Car {
public:
Car() {
m_A = 0;
m_B = 0;
}
//如果想让指针指向的值也不可以修改,需要声明常函数
void ShowPerson() const {
//this = NULL; // 错误:this指针是一个指针常量,指针的指向不可修改
//this->mA = 100; // 错误:常函数内不可以修改成员属性
//const修饰成员函数,可修改mutable修饰的变量
this->m_B = 100;
}
void MyFunc() const {
//mA = 10000;
}
public:
int m_A;
mutable int m_B; //可修改 可变的
};
int main() {
system("chcp 65001> nul");
const Car car; // 常对象
cout << car.m_A << endl; // 0
//car.m_A = 100; // 错误:常对象内成员属性不可修改
car.m_B = 200; // 正确:car是常对象,m_B是mutable修饰的成员属性,在常函数内依然可以修改
car.ShowPerson(); // 常对象只能调用常函数
//car.MyFunc(); // 错误:常对象不能调用非常函数
system("pause");
return 0;
}
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)