文章详情 学习记录

C++学习(一)

发布时间:2026-06-10 17:10 浏览次数:27 最后更新:2026-06-10 17:43

c++

正文内容

简介

输出/输入

变量/常量

关键字

asm else new this
auto enum operator throw
bool explicit private true
break export protected try
case extern public typedef
catch false register typeid
char float reinterpret_cast typename
class for return union
const friend short unsigned
const_cast goto signed using
continue if sizeof virtual
default inline static void
delete int static_cast volatile
do long struct wchar_t
double mutable switch while
dynamic_cast namespace template

数据类型

基本数据类型

C++ 为程序员提供了种类丰富的内置数据类型和用户自定义的数据类型。下表列出了七种基本的 C++ 数据类型:

类型 关键字
布尔型 bool
字符型 char
整型 int
浮点型 float
双浮点型 double
无类型 void
宽字符型 wchar_t

Note

其中 wchar_t 是这样来的:

typedef short int wchar_t;


一些基本类型可以使用一个或多个类型修饰符进行修饰:

修饰符 描述 示例
signed 表示有符号类型(默认) signed int x = -10;
unsigned 表示无符号类型 unsigned int y = 10;
short 表示短整型 short int z = 100;
long 表示长整型 long int a = 100000;
const 表示常量,值不可修改 const int b = 5;
volatile 表示变量可能被意外修改,禁止编译器优化 volatile int c = 10;
mutable 表示类成员可以在 const 对象中修改 mutable int counter;

下表显示了各种变量类型在内存中存储值时需要占用的内存,以及该类型的变量所能存储的最大值和最小值。

Important

注意:不同系统会有所差异,一字节为 8 位。

注意:默认情况下,int、short、long都是带符号的,即 signed。

注意:long int 8 个字节,int 都是 4 个字节,早期的 C 编译器定义了 long int 占用 4 个字节,int 占用 2 个字节,新版的 C/C++ 标准兼容了早期的这一设定。

数据类型 描述 大小(字节) 范围/取值示例
bool 布尔类型,表示真或假 1 truefalse
char 字符类型,通常用于存储 ASCII 字符 1 -128 到 127 或 0 到 255
signed char 有符号字符类型 1 -128 到 127
unsigned char 无符号字符类型 1 0 到 255
wchar_t 宽字符类型,用于存储 Unicode 字符 2 或 4 取决于平台
char16_t 16 位 Unicode 字符类型(C++11 引入) 2 0 到 65,535
char32_t 32 位 Unicode 字符类型(C++11 引入) 4 0 到 4,294,967,295
short 短整型 2 -32,768 到 32,767
unsigned short 无符号短整型 2 0 到 65,535
int 整型 4 -2,147,483,648 到 2,147,483,647
unsigned int 无符号整型 4 0 到 4,294,967,295
long 长整型 4 或 8 取决于平台
unsigned long 无符号长整型 4 或 8 取决于平台
long long 长长整型(C++11 引入) 8 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
unsigned long long 无符号长长整型(C++11 引入) 8 0 到 18,446,744,073,709,551,615
float 单精度浮点数 4 约 ±3.4e±38(6-7 位有效数字)
double 双精度浮点数 8 约 ±1.7e±308(15 位有效数字)
long double 扩展精度浮点数 8、12 或 16 取决于平台

C++11新增类型

数据类型 描述 示例
auto 自动类型推断 auto x = 10;
decltype 获取表达式的类型 decltype(x) y = 20;
nullptr 空指针常量 int* ptr = nullptr;
std::initializer_list 初始化列表类型 std::initializer_list<int> list = {1, 2, 3};
std::tuple 元组类型,可以存储多个不同类型的值 std::tuple<int, float, char> t(1, 2.0, 'a');

以下代码会会输出您电脑上各种数据类型的大小。

#include<iostream>  
#include <limits>
 
using namespace std;  
  
int main()  
{  
    cout << "type: \t\t" << "************size**************"<< endl;  
    cout << "bool: \t\t" << "所占字节数:" << sizeof(bool);  
    cout << "\t最大值:" << (numeric_limits<bool>::max)();  
    cout << "\t\t最小值:" << (numeric_limits<bool>::min)() << endl;  
    cout << "char: \t\t" << "所占字节数:" << sizeof(char);  
    cout << "\t最大值:" << (numeric_limits<char>::max)();  
    cout << "\t\t最小值:" << (numeric_limits<char>::min)() << endl;  
    cout << "signed char: \t" << "所占字节数:" << sizeof(signed char);  
    cout << "\t最大值:" << (numeric_limits<signed char>::max)();  
    cout << "\t\t最小值:" << (numeric_limits<signed char>::min)() << endl;  
    cout << "unsigned char: \t" << "所占字节数:" << sizeof(unsigned char);  
    cout << "\t最大值:" << (numeric_limits<unsigned char>::max)();  
    cout << "\t\t最小值:" << (numeric_limits<unsigned char>::min)() << endl;  
    cout << "wchar_t: \t" << "所占字节数:" << sizeof(wchar_t);  
    cout << "\t最大值:" << (numeric_limits<wchar_t>::max)();  
    cout << "\t\t最小值:" << (numeric_limits<wchar_t>::min)() << endl;  
    cout << "short: \t\t" << "所占字节数:" << sizeof(short);  
    cout << "\t最大值:" << (numeric_limits<short>::max)();  
    cout << "\t\t最小值:" << (numeric_limits<short>::min)() << endl;  
    cout << "int: \t\t" << "所占字节数:" << sizeof(int);  
    cout << "\t最大值:" << (numeric_limits<int>::max)();  
    cout << "\t最小值:" << (numeric_limits<int>::min)() << endl;  
    cout << "unsigned: \t" << "所占字节数:" << sizeof(unsigned);  
    cout << "\t最大值:" << (numeric_limits<unsigned>::max)();  
    cout << "\t最小值:" << (numeric_limits<unsigned>::min)() << endl;  
    cout << "long: \t\t" << "所占字节数:" << sizeof(long);  
    cout << "\t最大值:" << (numeric_limits<long>::max)();  
    cout << "\t最小值:" << (numeric_limits<long>::min)() << endl;  
    cout << "unsigned long: \t" << "所占字节数:" << sizeof(unsigned long);  
    cout << "\t最大值:" << (numeric_limits<unsigned long>::max)();  
    cout << "\t最小值:" << (numeric_limits<unsigned long>::min)() << endl;  
    cout << "double: \t" << "所占字节数:" << sizeof(double);  
    cout << "\t最大值:" << (numeric_limits<double>::max)();  
    cout << "\t最小值:" << (numeric_limits<double>::min)() << endl;  
    cout << "long double: \t" << "所占字节数:" << sizeof(long double);  
    cout << "\t最大值:" << (numeric_limits<long double>::max)();  
    cout << "\t最小值:" << (numeric_limits<long double>::min)() << endl;  
    cout << "float: \t\t" << "所占字节数:" << sizeof(float);  
    cout << "\t最大值:" << (numeric_limits<float>::max)();  
    cout << "\t最小值:" << (numeric_limits<float>::min)() << endl;  
    cout << "size_t: \t" << "所占字节数:" << sizeof(size_t);  
    cout << "\t最大值:" << (numeric_limits<size_t>::max)();  
    cout << "\t最小值:" << (numeric_limits<size_t>::min)() << endl;  
    cout << "string: \t" << "所占字节数:" << sizeof(string) << endl;  
    // << "\t最大值:" << (numeric_limits<string>::max)() << "\t最小值:" << (numeric_limits<string>::min)() << endl;  
    cout << "type: \t\t" << "************size**************"<< endl;  
    return 0;  
}

派生数据类型

数据类型 描述 示例
数组 相同类型元素的集合 int arr[5] = {1, 2, 3, 4, 5};
指针 存储变量内存地址的类型 int* ptr = &x;
引用 变量的别名 int& ref = x;
函数 函数类型,表示函数的签名 int func(int a, int b);
结构体 用户定义的数据类型,可以包含多个不同类型的成员 struct Point { int x; int y; };
用户定义的数据类型,支持封装、继承和多态 class MyClass { ... };
联合体 多个成员共享同一块内存 union Data { int i; float f; };
枚举 用户定义的整数常量集合 enum Color { RED, GREEN, BLUE };


类型别名

别名 描述 示例
typedef 为现有类型定义别名 typedef int MyInt;
using 为现有类型定义别名(C++11 引入) using MyInt = int;


标准库类型

数据类型 描述 示例
std::string 字符串类型 std::string s = "Hello";
std::vector 动态数组 std::vector<int> v = {1, 2, 3};
std::array 固定大小数组(C++11 引入) std::array<int, 3> a = {1, 2, 3};
std::pair 存储两个值的容器 std::pair<int, float> p(1, 2.0);
std::map 键值对容器 std::map<int, std::string> m;
std::set 唯一值集合 std::set<int> s = {1, 2, 3};

typedef 声明

您可以使用 typedef 为一个已有的类型取一个新的名字。下面是使用 typedef 定义一个新类型的语法:

typedef type newname; 

例如,下面的语句会告诉编译器,feet 是 int 的另一个名称:

typedef int feet;

现在,下面的声明是完全合法的,它创建了一个整型变量 distance:

feet distance;

类型转换

类型转换是将一个数据类型的值转换为另一种数据类型的值。

C++ 中有四种类型转换:静态转换、动态转换、常量转换和重新解释转换。

静态转换(Static Cast)

静态转换是将一种数据类型的值强制转换为另一种数据类型的值。

静态转换通常用于比较类型相似的对象之间的转换,例如将 int 类型转换为 float 类型。

静态转换不进行任何运行时类型检查,因此可能会导致运行时错误

int i = 10;
float f = static_cast<float>(i); // 静态将int类型转换为float类型

动态转换(Dynamic Cast)

动态转换(dynamic_cast)是 C++ 中用于在继承层次结构中进行向下转换(downcasting)的一种机制。

动态转换通常用于将一个基类指针或引用转换为派生类指针或引用。

动态转换在运行时进行类型检查。如果转换失败,对于指针类型会返回 nullptr,对于引用类型则会抛出 std::bad_cast 异常。

语法:

dynamic_cast<目标类型>(表达式)
  • 目标类型:必须是指针或引用类型。

  • 表达式:需要转换的基类指针或引用。

#include <iostream>

//以下为指针类型的动态转换

class Base {
public:
    virtual ~Base() = default; // 基类必须具有虚函数
};
 
class Derived : public Base {
public:
    void show() {
        std::cout << "Derived class method" << std::endl;
    }
};
 
int main() {
    Base* ptr_base = new Derived; // 基类指针指向派生类对象
 
    // 将基类指针转换为派生类指针
    Derived* ptr_derived = dynamic_cast<Derived*>(ptr_base);
 
    if (ptr_derived) {
        ptr_derived->show(); // 成功转换,调用派生类方法
    } else {
        std::cout << "Dynamic cast failed!" << std::endl;
    }
 
    delete ptr_base;
    return 0;
}

/*输出:
Derived class method
*/
#include <iostream>
#include <typeinfo>

//以下为引用类型的动态转换

class Base {
public:
    virtual ~Base() = default; // 基类必须具有虚函数
};

class Derived : public Base {
public:
    void show() {
        std::cout << "Derived class method" << std::endl;
    }
};

int main() {
    Derived derived_obj;
    Base& ref_base = derived_obj; // 基类引用绑定到派生类对象

    try {
        // 将基类引用转换为派生类引用
        Derived& ref_derived = dynamic_cast<Derived&>(ref_base);
        ref_derived.show(); // 成功转换,调用派生类方法
    } catch (const std::bad_cast& e) {
        std::cout << "Dynamic cast failed: " << e.what() << std::endl;
    }

    return 0;
}

/*输出:
Derived class method
*/
特性 指针类型 引用类型
转换失败返回值 返回 nullptr 抛出 std::bad_cast 异常
适用场景 向下转换、运行时类型检查 向下转换、运行时类型检查
性能开销 较高 较高
基类要求 必须具有虚函数 必须具有虚函数


常量转换(Const Cast)

常量转换用于将 const 类型的对象转换为非 const 类型的对象。

常量转换只能用于转换掉 const 属性,不能改变对象的类型。

const int i = 10;
int& r = const_cast<int&>(i); // 常量转换,将const int转换为int

重新解释转换(Reinterpret Cast)

重新解释转换将一个数据类型的值重新解释为另一个数据类型的值,通常用于在不同的数据类型之间进行转换。

重新解释转换不进行任何类型检查,因此可能会导致未定义的行为。

int i = 10;
float f = reinterpret_cast<float&>(i); // 重新解释将int类型转换为float类型

C++变量作用域

一般来说有三个地方可以定义变量:

  • 在函数或一个代码块内部声明的变量,称为局部变量
  • 在函数参数的定义中声明的变量,称为形式参数
  • 在所有函数外部声明的变量,称为全局变量

作用域是程序的一个区域,变量的作用域可以分为以下几种:

  • 局部作用域:在函数内部声明的变量具有局部作用域,它们只能在函数内部访问。局部变量在函数每次被调用时被创建,在函数执行完后被销毁。
  • 全局作用域:在所有函数和代码块之外声明的变量具有全局作用域,它们可以被程序中的任何函数访问。全局变量在程序开始时被创建,在程序结束时被销毁。
  • 块作用域:在代码块内部声明的变量具有块作用域,它们只能在代码块内部访问。块作用域变量在代码块每次被执行时被创建,在代码块执行完后被销毁。
  • 类作用域:在类内部声明的变量具有类作用域,它们可以被类的所有成员函数访问。类作用域变量的生命周期与类的生命周期相同。

Caution

注意:如果在内部作用域中声明的变量与外部作用域中的变量同名,则内部作用域中的变量将覆盖外部作用域中的变量。



初始化局部变量和全局变量:当局部变量被定义时,系统不会对其初始化,您必须自行对其初始化。定义全局变量时,系统会自动初始化为下列值:

数据类型 初始化默认值
int 0
char '\0'
float 0
double 0
pointer NULL

正确地初始化变量是一个良好的编程习惯,否则有时候程序可能会产生意想不到的结果。



C++常量

整数常量

85         // 十进制
0213       // 八进制 
0x4b       // 十六进制 
30         // 整数 
30u        // 无符号整数 
30l        // 长整数 
30ul       // 无符号长整数

//以下为合法与非法的展示
212         // 合法的
215u        // 合法的
0xFeeL      // 合法的
078         // 非法的:8 不是八进制的数字
032UU       // 非法的:不能重复后缀

浮点常量

3.14159       // 合法的 
314159E-5L    // 合法的 
510E          // 非法的:不完整的指数
210f          // 非法的:没有小数或指数
.e55          // 非法的:缺少整数或分数

字符常量

字符常量是括在单引号中。如果常量以 L(仅当大写时)开头,则表示它是一个宽字符常量(例如 L'x'),此时它必须存储在 wchar_t 类型的变量中。否则,它就是一个窄字符常量(例如 'x'),此时它可以存储在 char 类型的简单变量中。

字符常量可以是一个普通的字符(例如 'x')、一个转义序列(例如 '\t'),或一个通用的字符(例如 '\u02C0')。

在 C++ 中,有一些特定的字符,当它们前面有反斜杠时,它们就具有特殊的含义,被用来表示如换行符(\n)或制表符(\t)等。下表列出了一些这样的转义序列码:

转义序列 含义
\\ \ 字符
\' ' 字符
\" " 字符
\? ? 字符
\a 警报铃声
\b 退格键
\f 换页符
\n 换行符
\r 回车
\t 水平制表符
\v 垂直制表符
\ooo 一到三位的八进制数
\xhh . . . 一个或多个数字的十六进制数


字符串常量

C++ 提供了以下两种类型的字符串表示形式:

C 风格字符串:char name[] = “hello”;

C++ 引入的 string 类类型:string s = “hello”

注:

  • 使用c风格字符串时,数组大小应该要比所存元素大小大1,因为末尾会为此字符串自动添加一个空字符

  • string包含在<string>头文件中




定义常量

在 C++ 中,有两种简单的定义常量的方式:

  • 使用 #define 预处理器。
  • 使用 const 关键字。

#define 预处理器

语法:#define identifier value

#include <iostream>
using namespace std;
 
#define LENGTH 10   
#define WIDTH  5
#define NEWLINE '\n'
 
int main()
{
 
   int area;  
   
   area = LENGTH * WIDTH;
   cout << area;
   cout << NEWLINE;
   return 0;
}

const 关键字

语法:const type variable = value;

#include <iostream>
using namespace std;
 
int main()
{
   const int  LENGTH = 10;
   const int  WIDTH  = 5;
   const char NEWLINE = '\n';
   int area;  
   
   area = LENGTH * WIDTH;
   cout << area;
   cout << NEWLINE;
   return 0;
}

C++类型限定符

类型限定符提供了变量的额外信息,用于在定义变量或函数时改变它们的默认行为的关键字。

限定符 含义
const const 定义常量,表示该变量的值不能被修改。
volatile 修饰符 volatile 告诉该变量的值可能会被程序以外的因素改变,如硬件或其他线程。。
restrict restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。
mutable mutable 用于修饰类的成员变量。被 mutable 修饰的成员变量可以被修改,即使它们所在的对象是 const 的。
static 用于定义静态变量,表示该变量的作用域仅限于当前文件或当前函数内,不会被其他文件或函数访问。
register 用于定义寄存器变量,表示该变量被频繁使用,可以存储在CPU的寄存器中,以提高程序的运行效率。在 C11 中被标记为弃用(deprecated) 在 C17 中被正式移除。




流程控制

循环

判断



数组

一维数组

//方式1
数据类型 ArrName[Length];

//方式2
数据类型 ArrName[Length]={元素1, 元素2, ...};

//方式3
数据类型 ArrName[]={元素1, 元素2, ...};

数组名可以计算数组在内存中的长度:sizeof(数组名),单位为字节数

数组名可以得到数组中有多少个元素:sizeof(数组名)/ sizeof(数组名[0])

数组名代表了数组所在内存的首地址:数组名即地址

数组中第一个元素所在的地址(和数组首地址相等):&数组名[0]

二维数组

数据类型 ArrName[row][column];
数据类型 ArrName[row][column] = {{元素1, 元素2}, {元素3, 元素4}};
数据类型 ArrName[row][column] = {元素1, 元素2, 元素3, 元素4};
数据类型 ArrName[][column] = {元素1, 元素2, 元素3, 元素4};

//推荐第二种方式
  • 输出时用两层for循环,外层打印行,内层打印列

  • 数组名可以用于查看数组所占的内存空间:sizeof(数组名),单位为字节数

  • 数组第一行所占的内存空间为:sizeof(数组名[0])

  • 数组第一个元素所占的内存空间为:sizeof(数组名[0][0])

  • 二维数组的行数:sizeof(数组名)/ sizeof(数组名[0])

  • 二维数组的列数:sizeof(数组名[0])/ sizeof(数组名[0][0])

  • 数组名可以获取数组的首地址: 数组名

  • 第一个元素的地址(和数组首地址相等):&数组名[0][0]

  • 第一行的首地址(和数组首地址相等):数组名[0]

  • 第二行的首地址(和数组首地址相等):数组名[1]

函数

返回值类型 函数名 参数列表
{
	语句...
	return 返回值
}
通用步骤:
创建后缀名为.h的头文件
创建后缀名为.cpp的源文件
在头文件中写函数声明
在源文件中写函数定义
  • 函数可以多次声明,但只能定义一次

函数的默认参数

// 声明
int test(int a, int b);

// 实现
int test(int a, int b = 10)
{
    return a + b;
}
  • 如果某个位置已经有了默认参数,那么从这个位置开始,之后的形参都必须有默认值
  • 函数的声明中已经写了默认参数,则函数的实现中则不可重复写默认参数,反之亦成立。

函数的占位参数

// 第二个int用于占位
int test(int a, int)
{
    return a;
}
  • 包含占位形参的函数使用时必须传入对应类型的数据,否则会报错

image-20260608133251536

  • 占位参数还可以有默认值,此时则不必传入对应类型的数据
int test(int a, int = 20)
{
    return a;
}

函数的重载

多个函数可以具有相同的函数名,提高函数的复用性

函数重载需满足的条件:

  • 同一个作用域下
  • 函数名相同
  • 函数的参数列表不同


指针

定义指针

语法:数据类型 * 指针名

取变量的地址:&变量名

使用指针

通过解引用的方式来使用指针指向的内存中的数据

*指针名即代表解引用

指针所占的内存空间

32位操作系统中指针占4个字节的内存空间

64位操作系统中指针占8个字节的内存空间

空指针/野指针

空指针如:int * p = NULL;

注意:空指针指向的内存是不可访问的(指向了编号为0的空间)

野指针:指针变量指向了非法的内存空间

如:int * p = (int *)0x1100;

注意:野指针是不可访问的

const与指针

  • const修饰指针(常量指针):指针的指向可以修改,指针指向的值不可修改

  • const修饰常量(指针常量):指针的指向不可修改,指针指向的值可以修改

  • const及修饰指针又修饰常量:指针的指向和指向的值都不可修改

数组与指针

函数和指针

值传递和引用传递



引用

引用的基本用途

引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。

一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。

引用必须在定义时初始化,并且一旦绑定到一个变量后,就不能再绑定到其他变量。



语法:数据类型 &别名 = 原名

引用的注意事项

  • 引用必须初始化
  • 引用在初始化后,不可以改变

引用做函数参数

引用做函数返回值

  • 不可返回局部变量的引用
  • 函数的调用可以作为左值(即引用可以作为左值)


常量引用

在函数的形参列表中,引用前可以添加const修饰符,添加const后传入的引用则不可作为左值,从而防止在函数内部不小心修改了引用值导致函数外部产生变化。





指针VS引用

引用很容易与指针混淆,它们之间有三个主要的不同:

  • 不存在空引用,引用必须连接到一块合法的内存。
  • 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
  • 引用必须在创建时被初始化。指针可以在任何时间被初始化。
  • 引用的对象必须是一个变量,而指针必须是一个地址。
特性 引用 指针
定义与初始化 必须初始化,且不能为 null 可以不初始化,可以在后续代码中指向其他对象,可以为 null
语法 使用 & 声明,例如:int &ref = a; 使用 * 声明,例如:int *ptr = &a;
重新绑定 不能重新绑定,一旦初始化后始终引用同一个对象。 可以重新指向其他对象,例如:ptr = &b;
空值(Nullability) 不能为 null,必须绑定到有效的对象。 可以为 null,表示不指向任何对象。
内存占用 不占用额外内存(编译器通常将其优化为直接操作所引用的对象)。 占用额外内存(存储地址,通常是一个机器字长,如4字节或8字节)。
访问方式 直接使用,无需解引用操作符,例如:ref = 10; 需要使用 * 解引用操作符访问或修改所指向的对象,例如:*ptr = 10;
多级间接访问 不支持多级间接访问(不能有引用的引用)。 支持多级间接访问(如指针的指针:int **pptr;)。
函数参数传递 常用于函数参数传递,语法简洁,例如:void func(int &x) { x = 10; } 也可以用于函数参数传递,但需要使用解引用操作符,例如:void func(int *x) { *x = 10; }
数组与引用 不能直接创建引用数组,但可以创建数组的引用,例如:int (&ref)[10] = arr; 可以创建指针数组,也可以创建指向数组的指针,例如:int *ptrArr[10];
安全性 更安全,不能为 null,且语法更直观。 更灵活,但容易出错(如空指针、野指针等)。
底层实现 通常通过指针实现,但编译器会优化为直接操作所引用的对象。 直接存储目标对象的内存地址。


结构体

定义

在 C++ 中,struct 语句用于定义结构体(structure)。

结构体是一种用户自定义的数据类型,用于将不同类型的数据组合在一起。与类(class)类似,结构体允许你定义成员变量和成员函数。

为了定义结构,您必须使用 struct 语句。struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下:

struct type_name {
member_type1 member_name1;
member_type2 member_name2;
member_type3 member_name3;
.
.
} object_names;

type_name 是结构体类型的名称,member_type1 member_name1 是标准的变量定义,比如 int i; 或者 float f; 或者其他有效的变量定义。在结构定义的末尾,最后一个分号之前,您可以指定一个或多个结构变量,这是可选的。下面是声明一个结构体类型 Books,变量为 book

struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book;

结构体优点:

  • 简单数据封装:适合封装多种类型的简单数据,通常用于数据的存储。
  • 轻量级:相比 class,结构体语法更简洁,适合小型数据对象。
  • 面向对象支持:支持构造函数、成员函数和访问权限控制,可以实现面向对象的设计。

具体用法如下所示:

using namespace std;
void printBook( struct Books book );

struct Books{
    char title[50];
    char author[50];
    char subject[100];
    int book_id;
};

int main(){
    struct Books Book1;//声明 Book1,类型为 Book
    struct Books Book2;//声明 Book2,类型为 Book

    // Book1 详述
    strcpy( Book1.title, "Learn C++ Programming");
    strcpy( Book1.author, "Chand Miyan");
    strcpy( Book1.subject, "C++ Programming");
    Book1.book_id = 6495407;

    //Book2 详述
    strcpy( Book2.title, "Telecom Billing");
    strcpy( Book2.author, "Yakit Singha");
    strcpy( Book2.subject, "Telecom");
    Book2.book_id = 6495700;

    //输出 Book1 信息
    printBook( Book1 );

    //输出 Book2 信息
    printBook( Book2 );

    return 0;
}



void printBook( struct Books book ){
cout << "Book title : " << book.title << endl;
cout << "Book author :" << book.author << endl;
cout << "Book subject :" << book.subject << endl;
cout << "Book id :" << book.book_id << endl;
}

指向结构的指针

您可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似,如下所示:

struct Books *struct_pointer;

以上代码定义了一个指向 Books 结构体的指针 struct_pointer。

现在,您可以在上述定义的指针变量中存储结构变量的地址。为了查找结构变量的地址,请把 & 运算符放在结构名称的前面,如下所示:

struct_pointer = &Book1;

以上代码将 Book1 结构体变量的地址赋值给 struct_pointer。

为了使用指向该结构的指针访问结构的成员,您必须使用 -> 运算符,如下所示:

struct_pointer->title;

以上代码通过 struct_pointer 访问 Book1 结构体的 title 成员。

具体用法如下所示:

void printBook( struct Books *book );

struct Books
{
	char title[50];
    char author[50];
    char subject[100];
    int book_id;
};

int main( )
{
    struct Books Book1;//声明 Book1,类型为 Book
    struct Books Book2;//声明 Book2,类型为 Book

    //Book1 详述
    strcpy( Book1.title, "Learn C++ Programming");
    strcpy( Book1.author, "Chand Miyan");
    strcpy( Book1.subject, "C++ Programming");
    Book1.book_id = 6495407;

    //Book2 详述
    strcpy( Book2.title, "Telecom Billing");
    strcpy( Book2.author, "Yakit Singha");
    strcpy( Book2.subject, "Telecom");
    Book2.book_id = 6495700;

    // 通过传 Book1 的地址来输出 Book1 信息
    printBook( &Book1 );

    //通过传 Book2 的地址来输出 Book2 信息
    printBook( &Book2 );

    return 0;
}

// 该函数以结构指针作为参数
void printBook( struct Books *book )
{
    cout << "Book title : " << book->title << endl;
    cout << "Book author : " << book->author << endl;
    cout << "Book subject :" << book->subject << endl;
    cout << "Book id :" << book->book_id << endl;
}

内存模型

四个区域:代码区、全局区、栈区、堆区

代码区:存放函数体的二进制代码,由操作系统管理(运行前即存在)

全局区:存放全局变量和静态变量以及常量(运行前即存在)

栈区:由编译器自动分配释放,存放函数的参数值,局部变量等等(运行后才存在)

堆区:由程序员分配和释放,若未释放则程序结束后由操作系统回收(运行后才存在)

代码区

代码区是共享的:目的是对于频繁被执行的程序,只需要在内存中有一份代码即可

代码区是只读的:防止程序意外的修改了它的指令

全局区

存放全局变量静态变量以及常量

该区域的数据在程序结束后由操作系统释放

栈区

注意不要返回局部变量的地址,栈区开辟的数据由编译器自动的释放

堆区

C++主要是利用new在堆区开辟内存

new关键字

利用new在堆中开辟内存,利用delete释放开辟的内存(利用new创建的数据会返回该数据对应类型的指针)





面向对象-封装

三大特性:封装、继承、多态

基本定义形态:

class Test
{
public: //访问权限,其下的所有成员均为public
    //字段
    int _length;
    //方法
    int test(int a, int b)
    {
        return a + b;
    }
    
private://访问权限,其下的所有成员均为private
    int _maxLength = 30;
};
//注意结尾处有个;

访问权限



  • public:公共权限,外部可以访问
  • protected:保护权限,外部仅子类可访问
  • private:私有权限,外部不可访问

Tip

一般将字段设置为私有,然后通过方法来控制字段的读写属性





class和struct的区别

  • struct默认权限为公共
  • class默认权限为私有


对象的初始化和清理

C++利用构造函数析构函数来实现对象的初始化和清理,这两个函数将被编译器自动调用,完成对象的初始化和清理工作。如果我们没有提供构造函数和析构函数,则编译器会自动为我们提供这两个函数的空实现



构造函数:

className()
{
	
}
  • 无返回值且无需写void
  • 函数名与类名相同
  • 构造函数亦可重载
  • 创建对象时自动调用,无需手动调用,且只会自动调用一次


析构函数:

~className()
{
	
}
  • 无返回值且无需写void
  • 函数名与类名相同,前面加上~符号
  • 无参数,不可重载
  • 销毁对象时自动调用,无需手动调用,且只会自动调用一次


构造函数的分类

  • 普通构造函数
//无参构造函数
className()
{
	length = 20;
}

//有参构造函数
className(int a)
{
	length = a;
}

//调用
int main()
{
    ClassName c1;//调用无参构造函数时不可写“()”,会被编译器认定为函数声明
    ClassName c2(10);
    ClassName c3 = ClassName(10);
    ClassName c4 = 10;//等价于ClassName c4 = ClassName(10);
    ClassName(13);//匿名对象
}

  • 拷贝构造函数
//构造函数
className(const ClassName &class)
{
	length = class.length;
}

//调用
int main()
{
    ClassName c1(10);
    ClassName c2(c1);//可以方便与将某个对象的属性批量拷贝
}

Caution

  • 单行语句ClassName(13);被称之为匿名对象,特征为语句执行完毕后,系统会立即回收掉匿名对象(即执行完构造函数后立即执行析构函数,然后再执行后面的语句)。
  • 不可使用拷贝构造函数来初始化匿名对象,ClassName(c1)等价为ClassName c1,会被认定为重复定义。


拷贝构造函数的调用

拷贝构造函数通常有三种调用情况:

  • 使用一个已经创建完毕的对象来初始化一个新对象
Person p1(20);
Person p2(p1); //此处调用了拷贝构造函数
  • 值传递的方式给函数参数传值
void doWork(Person person)
{
    
}

void Test()
{
    Person p;  //此处调用了默认构造函数
    doWork(p); //此处调用了拷贝构造函数
}
  • 以值方式返回局部对象
Person doWork()
{
    Person p;  //此处调用了默认构造函数
    return p;
}

void Test()
{
    Person person = doWork(); //此处调用了拷贝构造函数
}

构造函数的调用规则

默认情况下,c++编译器至少给一个类添加3个函数

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝


构造函数调用规则如下:

  • 如果用户定义有参构造函数,c++不再提供默认无参构造,但是会提供默认拷贝构造函数

  • 如果用户自定义了拷贝构造函数,c++不会再提供其他构造函数



深拷贝与浅拷贝



  • 浅拷贝:简单的赋值拷贝操作

Note

class Person
{
public:
    Person()
    {
        cout << "Person的默认构造函数调用” << endl;
    }

    Person(int age, int height)
    {
        m_Age = age;
        //利用new开辟在堆区,用指针接收
        m_Height = new int(height);
        cout << "Person的有参构造函数调用” << endl;
    }

    ~Person()
    {
  	//析构代码,将堆区开辟数据做释放操作
        if (m_Height != NULL)
        {
            delete m_Height;
            m_Height = NULL;
        }
    	cout << "Person的析构函数调用” << endl;
    }
    
    int m_Age;
    int *m_Height;
};

//调用,当此方法生命周期结束时,Person的析构函数会被调用两次
void test()
{
    Person p1(18, 170);
    Person p1(p2);
}

浅拷贝带来的问题是堆区的内存会在析构函数中重复释放,从而产生非法操作。通过自己实现拷贝构造函数来解决浅拷贝带来的问题。



  • 深拷贝:在堆区重新申请空间,来进行拷贝操作
//自己实现拷贝构造函数,解决浅拷贝带来的问题
Person (const Person &p)
{
	cout << "Person 拷贝构造函数调用” << endl;
    m_Age = p.m_Age;
    //m_Height=p.m_Height; //编译器默认实现就是这行代码
    
    m_Height = new int(*p.m_Height);//深拷贝操作,堆区重新申请空间接收p.m_Height的值
}

初始化列表



作用:C++提供了初始化列表语法,用来初始化属性

语法:构造函数(): 属性1(值1), 属性2(值2) ….. {}

//方法1
class Person
{
public:
    Person(int a, int b, int c)
    {
		m_A = a;
		m_B = b;
        m_C = c;
    }
	
    int m_A;
    int m_B;
    int m_C;
};

//方法2
class Person
{
public:
    Person(): m_A(10), m_B(20), m_C(30)
    {
        
    }
	
    Person(int a, int b, int c): m_A(a), m_B(b), m_C(c)
    {
        
    }
    
    int m_A;
    int m_B;
    int m_C;
};

对象作为类成员

当其它类对象作为本类成员时:构造时先构造其他类对象,再构造自身;析构时先析构自身,再析构其它类对象。



类中的静态成员



静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员

静态成员分为:



  • 静态成员变量

    • 所有对象共享同一份数据

    • 在编译阶段分配内存

    • 类内声明,类外初始化

  • 静态成员函数

    • 所有对象共享同一个函数

    • 静态成员函数只能访问静态成员变量

class Person
{
public:
    static int m_A;
    int m_B;
    
    static void func()
    {
        m_A = 0; // 静态成员函数可以访问静态成员变量
        //m_B = 0; //此语句编译器报错,因为静态成员函数只能访问静态成员变量
        cout << "static void func()被调用" << endl;
    }
};

int Person::m_A = 100;


//静态成员有两种访问方式
void test()
{
	//1、通过对象进行访问
    Person p;
    cout << p.m_A << endl;
    p.func();

    //2、通过类名进行访问
    cout << Person::m_A << endl;
    Person::func();
}

C++对象模型和this指针



类的对象所占空间大小

空类的对象占用的内存空间为1个字节且具有自己的独有的内存地址。

//每个对象占1字节
class Person
{
	
};

但若类本身不为空,则占用空间为实际类中所有非静态成员变量的占用空间

//每个对象占4字节
class Person
{
	int length;
    //静态成员变量
    static int Number;
    //这是成员函数,不是成员变量
    void func()
    {
        
    }
};

this指针

作用:

  • 当形参和成员变量同名时,可用this指针来区分,从而解决名称冲突
class Person
{
public:
    Person (int age)
	{
    	this->age = age;//若不适用this,则无法识别到类中自己的age,从而导致age未初始化
    }

    int age;
}:

/*
关于 -> 符号:
p->name 完全等价于 (*p).name
*p:先解引用指针,拿到指针指向的对象
.:再访问对象成员
-> 就是简化写法,更简洁、更常用
简单记:指针用 ->,普通对象用 .
*/
  • 在类的非静态成员函数中返回对象本身,可使用return *this
class Person
{
public:
    Person (int age)
	{
    	this->age = age;
    }
    
    void PersonaddAge (Person &p)
	{
    	this->age += p.age;
    }

    Person& PersonaddAge (Person &p)
	{
    	this->age += p.age;
        return *this; //拿到Person的本体
        //返回类型应为引用类型,若不返回引用类型,则返回的是新创建的Person对象,而不是本身了
    }
    
    int age;
}:

const修饰成员函数



常函数:

  • 成员函数后加const后我们称为这个函数为常函数

  • 常函数内不可以修改成员属性

  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改

class Person
{
public:
    void func() const
	{
    	//m_A = 100; //此语句会报错,在此函数中无法修改m_A
        m_B = 10;
    }

    int m_A;
    mutable int m_B;
}:

常对象

  • 声明对象前加const称该对象为常对象

  • 常对象只能调用常函数

void test()
{
    const Person p; //在对象前加上const关键字
    //p.m_A = 100; //此语句会报错,无法修改m_A
    p.m_B = 100;
    p.func();
}

友元

在程序里,有些私有属性,想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术。友元的目的就是让一个函数或者类,访问另一个类中私有成员。友元的关键字为friend

友元的三种实现

  • 全局函数做友元

  • 类做友元

  • 成员函数做友元



全局函数做友元

修改前:

//类
class Building
{
public:
    building()
    {
        m_SittingRoom = "客厅";
        m_BedRoom = "卧室";
    }
    
public:
    string m_SittingRoom;
    
private:
    string m_BedRoom;
};

//全局函数
void GoodFriend(Building& b)
{
	cout << "好基友全局函数 正在访问:" << building->m_SittingRoom << endl;
    //此时是无法访问Building类中的m_BedRoom属性
	//cout << "好基友全局函数 正在访问:" << building->m_BedRoom << endl;
}

修改后:

//类
class Building
{
    //增加friend关键字后,可以指定GoodFriend方法,访问Building类的私有成员
    friend void GoodFriend(Building& b);
    
public:
    building()
    {
        m_SittingRoom = "客厅";
        m_BedRoom = "卧室";
    }
    
public:
    string m_SittingRoom;
    
private:
    string m_BedRoom;
};

//全局函数
void GoodFriend(Building& b)
{
	cout << "好基友全局函数 正在访问:" << building->m_SittingRoom << endl;
    //此时可以访问Building类中的m_BedRoom属性
	cout << "好基友全局函数 正在访问:" << building->m_BedRoom << endl;
}

类做友元

修改前:

//类
class Building
{
public:
    building()
    {
        m_SittingRoom = "客厅";
        m_BedRoom = "卧室";
    }
    
public:
    string m_SittingRoom;
    
private:
    string m_BedRoom;
};

class GoodFriend
{
public:
    GoodFriend()
    {
		b = new Building;
    }
    void Visit(); //声明Visit方法
    Building *b;
};

//定义Visit方法
GoodFriend::Visit()
{
	cout << "好基友全局函数 正在访问:" << building->m_SittingRoom << endl;
    //此时是无法访问Building类中的m_BedRoom属性
	//cout << "好基友全局函数 正在访问:" << building->m_BedRoom << endl;
}

修改后

//类
class Building
{
    //增加friend关键字后,可以指定GoodFriend类,访问Building类的私有成员
    friend class GoodFriend;
    
public:
    building()
    {
        m_SittingRoom = "客厅";
        m_BedRoom = "卧室";
    }
    
public:
    string m_SittingRoom;
    
private:
    string m_BedRoom;
};

class GoodFriend
{
public:
    GoodFriend()
    {
		b = new Building;
    }
    void Visit(); //声明Visit方法
    Building *b;
};

//定义Visit方法
GoodFriend::Visit()
{
	cout << "好基友全局函数 正在访问:" << building->m_SittingRoom << endl;
    //此时可以访问Building类中的m_BedRoom属性
	cout << "好基友全局函数 正在访问:" << building->m_BedRoom << endl;
}

成员函数做友元

//类
class Building
{
    //增加friend关键字后,可以指定GoodFriend类下的Visit,访问Building类的私有成员
    friend class GoodFriend::Visit();
    
public:
    building()
    {
        m_SittingRoom = "客厅";
        m_BedRoom = "卧室";
    }
    
public:
    string m_SittingRoom;
    
private:
    string m_BedRoom;
};

class GoodFriend
{
public:
    GoodFriend()
    {
		b = new Building;
    }
    void Visit(); //声明Visit方法
    Building *b;
};

//定义Visit方法
GoodFriend::Visit()
{
	cout << "好基友全局函数 正在访问:" << building->m_SittingRoom << endl;
    //此时可以访问Building类中的m_BedRoom属性
	cout << "好基友全局函数 正在访问:" << building->m_BedRoom << endl;
}

面向对象-继承

基本形态:

//父类
class BaseClass
{
    //...
};

//子类
class ConcreteClass : public BaseClass
{
    
};

继承方式



继承的语法:class 子类 : 继承方式 父类



继承方式一共有三种:

  • 公共继承

  • 保护继承

  • 私有继承

classDiagram %% 基类A class A{ +int a #int b -int c } %% 公有继承子类 class B_Public["class B : public A"]{ +int a #int b -[不可访问] int c } %% 保护继承子类 class C_Protected["class C : protected A"]{ #int a #int b -[不可访问] int c } %% 私有继承子类 class D_Private["class D : private A"]{ -int a -int b -[不可访问] int c } %% 继承连线 + 标注继承方式 B_Public <|-- A : 公有继承 C_Protected <|-- A : 保护继承 D_Private <|-- A : 私有继承

继承中的对象模型

class BaseClass
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

/*
ConcreteClass实际占用16个字节
意味着子类拿到了父类的所有非静态成员属性
只不过把有些东西隐藏了,使其不可见
*/
class ConcreteClass : public BaseClass
{
public:
	int m_D;
};

构造和析构顺序



  • 在新建一个子类对象时,程序会先调用父类的构造函数,然后再调用子类的构造函数

  • 程序清理对象时,会先调用子类的析构函数,然后再调用父类的析构函数



继承中同名成员的处理方式



  • 访问子类同名成员,直接访问即可
  • 访问父类同名成员,需要加作用域
  • 如果子类中出现和父类同名的方法,子类的同名成员会隐藏掉父类中所有同名方法(包括重载的方法)
class BaseClass
{
public:
	int m_A = 100;
    static int m_B;
    
    void func()
    {
        cout << "父类" << endl;
    }
};
int BaseClass::m_B = 100;

class ConcreteClass : public BaseClass
{
public:
	int m_A = 200;
    static int m_B;
    
    void func()
    {
        cout << "子类" << endl;
    }
};
int ConcreteClass::m_B = 200;

void Test()
{
    ConcreteClass c;
    
    c.func();//调用的子类方法
    c.BaseClass::func();//调用的父类方法
    
	cout << ConcreteClass::m_B << endl;//结果为子类的200
    cout << BaseClass::m_B << endl;//结果为父类的100
    cout << ConcreteClass::BaseClass::m_B << endl;//结果为父类的100
    
    cout << c.m_A << endl;//结果为子类的200
    cout << c.BaseClass::m_A << endl;//结果为父类的100
}

多继承语法



语法:class 子类 : 继承方式 父类1, 继承方式 父类2, ...



Note

多继承可能会出现多个父类中存在同名成员的情况,需要加作用域来区分。



菱形继承



菱形继承概念:

两个派生类继承同一个基类

又有某个类同时继承这两个派生类

这种继承被称为菱形继承,或者钻石继承

graph TD A[基类:动物Animal] --> B[派生类:羊Sheep] A --> C[派生类:骆驼Camel] B --> D[最终派生类:羊驼Alpaca] C --> D

菱形继承问题:

  1. 羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性。

  2. 羊驼继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。

class Animal
{
public:
	int Age;
};

class Sheep : public Animal
{
	
};

class Camel : public Animal
{
	
};

class Alpaca : public Sheep, public Camel
{
	
};

void Test()
{
    Alpaca a;
    //菱形继承父类拥有相同数据,需要加作用域来区分
    //a.Age = 10; //编译器报错
    a.Sheep::Age = 10;
    a.Camel::Age = 20;
    //但是实际上Alpaca只需要一份Age就行
}

Important

利用虚继承可以解决此问题,继承之前加关键字Virtual可将继承变为虚继承

class Animal
{
public:
	int Age;
};

class Sheep : virtual public Animal
{
	
};

class Camel : virtual public Animal
{
	
};

class Alpaca : public Sheep, public Camel
{
	
};

void Test()
{
    Alpaca a;
    
    a.Sheep::Age = 10;
    a.Camel::Age = 20; 
    //此时Age最终的值只会跟随最后一次赋值的值(与作用域无关),相当于Age只会存在一份
    //其原理是使用了“虚基类指针”,将实际继承变为了指针指向父类中的数据
}

面向对象-多态



多态分为两类

  • 静态多态:函数重载运算符重载属于静态多态,复用函数名

  • 动态多态:派生类虚函数实现运行时多态

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定—编译阶段确定函数地址

  • 动态多态的函数地址晚绑定—运行阶段确定函数地址

class Animal
{
public:
	void Speak()
    {
		cout << "动物在说话" << endl;
    }
};

class Cat : public Animal
{
public:
	void Speak()
    {
		cout << "猫在说话" << endl;
    }
};

void DoSpeak(Animal &animal)
{
    animal.Speak();
}

void Test()
{
    Cat c;
    //实际输出为"动物在说话",即地址早绑定,因为在DoSpeak调用的是animal.Speak();
    DoSpeak(c);
}

将Animal类改为如下即可:

class Animal
{
public:
    //改为虚函数,即可实现动态多态
	virtual void Speak()
    {
		cout << "动物在说话" << endl;
    }
};

以上实现即:在子类中重写父类的虚方法即可实现动态多态



纯虚函数和抽象类



在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数。

纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0;

当类中存在纯虚函数,这个类也称为抽象类

抽象类特点:

  • 无法实例化对象

  • 子类必须重写抽象类中的纯虚函数,否则此子类也属于抽象类



虚析构与纯虚析构



多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。



解决方式:将父类中的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象

  • 都需要有具体的函数实现

虚析构和纯虚析构区别:

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象


虚析构语法:virtual ~类名() {}

纯虚析构语法:virtual ~类名() = 0;且类外要实现:类名::~类名() {}

class Animal
{
public:
    Animal()
    {
		cout << "Base Constructor" << endl;
    }
	virtual void Speak() = 0;
    
    ~Animal()
    {
        cout << "Base Destructor" << endl;
    }
};

class Cat : public Animal
{
public:
    Cat(string name)
    {
        _name = new string(name);
        cout << "Derived Constructor" << endl;
    }
    
	virtual void Speak()
    {
		cout << *_name << " Cat Speak!" << endl;
    }
    
    string* _name;
    
    ~Cat()
    {
        if(_name != NULL)
        {
            delete _name;
            _name = NULL;
            cout << "Derived Destructor" << endl;
        }
    }
};

void Test()
{
    Animal* animal = new Cat("Tom");
    animal->Speak();
    delete animal;
}

/*输出如下所示:可见子类的析构函数并未被调用,导致内存泄露!
Base Constructor
Derived Constructor
Tom Cat Speak!
Base Destructor
*/


//为解决以上问题,将父类改为如下所示即可
//此时父类的指针释放可调用子类的析构函数
class Animal
{
public:
    Animal()
    {
		cout << "Base Constructor" << endl;
    }
	virtual void Speak() = 0;
    
    virtual ~Animal()
    {
        cout << "Base Destructor" << endl;
    }
};

对于纯虚析构方法,既要有声明,也需要有实现,父类可改为如下所示:

class Animal
{
public:
    Animal()
    {
		cout << "Base Constructor" << endl;
    }
	virtual void Speak() = 0;
    
    virtual ~Animal() = 0;//纯虚析构方法
};

//若无此实现,代码将会报错
Animal::~Animal()
{
    cout << "Base Destructor" << endl;
}

文件操作



程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放,通过文件可以将数据持久化。

要在 C++ 中进行文件处理,必须在 C++ 源代码文件中包含头文件 <iostream> 和 。



文件类型分为两种:

  1. 文本文件:文件以文本的ASCII码形式存储在计算机中
  2. 二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们


操作文件的三大类:

数据类型 描述
ofstream 该数据类型表示输出文件流,用于创建文件并向文件写入信息。
ifstream 该数据类型表示输入文件流,用于从文件读取信息。
fstream 该数据类型通常表示文件流,且同时具有 ofstream 和 ifstream 两种功能,这意味着它可以创建文件,向文件写入信息,从文件读取信息。


打开文件

在从文件读取信息或者向文件写入信息之前,必须先打开文件。ofstreamfstream 对象都可以用来打开文件进行写操作,如果只需要打开文件进行读操作,则使用 ifstream 对象。

下面是 open() 函数的标准语法,open() 函数是 fstream、ifstream 和 ofstream 对象的一个成员。

void open(const char *filename, ios::openmode mode);

在这里,open() 成员函数的第一参数指定要打开的文件的名称和位置,第二个参数定义文件被打开的模式。

模式标志 描述
ios::app 追加模式。所有写入都追加到文件末尾。
ios::ate 文件打开后定位到文件末尾。
ios::in 打开文件用于读取。
ios::out 打开文件用于写入。
ios::trunc 如果该文件已经存在,其内容将在打开文件之前被截断,即把文件长度设为 0。

您可以把以上两种或两种以上的模式结合使用。例如,如果您想要以写入模式打开文件,并希望截断文件,以防文件已存在,那么您可以使用下面的语法:

ofstream outfile;
outfile.open("file.dat", ios::out | ios::trunc );

类似地,您如果想要打开一个文件用于读写,可以使用下面的语法:

ifstream afile;
afile.open("file.dat", ios::out | ios::in );

关闭文件

当 C++ 程序终止时,它会自动关闭刷新所有流,释放所有分配的内存,并关闭所有打开的文件。但程序员应该养成一个好习惯,在程序终止前关闭所有打开的文件。

下面是 close() 函数的标准语法,close() 函数是 fstream、ifstream 和 ofstream 对象的一个成员。

void close();

写入文件

在 C++ 编程中,我们使用流插入运算符( << )向文件写入信息,就像使用该运算符输出信息到屏幕上一样。唯一不同的是,在这里您使用的是 ofstreamfstream 对象,而不是 cout 对象。



读取文件

在 C++ 编程中,我们使用流提取运算符( >> )从文件读取信息,就像使用该运算符从键盘输入信息一样。唯一不同的是,在这里您使用的是 ifstreamfstream 对象,而不是 cin 对象。



读取 & 写入实例

下面的 C++ 程序以读写模式打开一个文件。在向文件 afile.dat 写入用户输入的信息之后,程序从文件读取信息,并将其输出到屏幕上:

#include <fstream>
#include <iostream>
using namespace std;
 
int main() {
    
   char data[100];
 
   // 以写模式打开文件
   ofstream outfile;
   outfile.open("afile.dat");
 
   cout << "Writing to the file" << endl;
   cout << "Enter your name: "; 
   cin.getline(data, 100);
 
   // 向文件写入用户输入的数据
   outfile << data << endl;
 
   cout << "Enter your age: "; 
   cin >> data;
   cin.ignore();
   
   // 再次向文件写入用户输入的数据
   outfile << data << endl;
 
   // 关闭打开的文件
   outfile.close();
 
   // 以读模式打开文件
   ifstream infile; 
   infile.open("afile.dat"); 
 
   cout << "Reading from the file" << endl; 
   infile >> data; 
 
   // 在屏幕上写入数据
   cout << data << endl;
   
   // 再次从文件读取数据,并显示它
   infile >> data; 
   cout << data << endl; 
 
   // 关闭打开的文件
   infile.close();
 
   return 0;
}

/*以上代码输出如下所示:
$./a.out
Writing to the file
Enter your name: Zara
Enter your age: 9
Reading from the file
Zara
9
*/

上面的实例中使用了 cin 对象的附加函数,比如 getline()函数从外部读取一行,ignore() 函数会忽略掉之前读语句留下的多余字符。



文件位置指针

istreamostream 都提供了用于重新定位文件位置指针的成员函数。这些成员函数包括关于 istream 的 seekg("seek get")和关于 ostream 的 seekp("seek put")。

seekg 和 seekp 的参数通常是一个长整型。第二个参数可以用于指定查找方向。查找方向可以是 ios::beg(默认的,从流的开头开始定位),也可以是 ios::cur(从流的当前位置开始定位),也可以是 ios::end(从流的末尾开始定位)。

文件位置指针是一个整数值,指定了从文件的起始位置到指针所在位置的字节数。下面是关于定位 "get" 文件位置指针的实例:

// 定位到 fileObject 的第 n 个字节(假设是 ios::beg)
fileObject.seekg( n );
 
// 把文件的读指针从 fileObject 当前位置向后移 n 个字节
fileObject.seekg( n, ios::cur );
 
// 把文件的读指针从 fileObject 末尾往回移 n 个字节
fileObject.seekg( n, ios::end );
 
// 定位到 fileObject 的末尾
fileObject.seekg( 0, ios::end );

正文内容已启用复制保护,代码块与行内代码仍支持复制。

评论区

欢迎参与讨论,评论提交与展示状态会在下方同步更新。

发表评论

提交后将进入人工审核,审核通过后才会展示。

邮箱仅用于必要的联系与基础风控,不会在页面中公开展示。

请文明发言,避免发布广告、链接或敏感内容;短时间内频繁提交会被限制。

隐私提示:提交评论即表示你同意站点为评论展示、审核、防刷与安全风控目的处理你填写的昵称、邮箱和评论内容。

其中邮箱不会公开展示,也不会用于与评论无关的公开用途。

读者留言

还没有评论,来发表第一条评论吧。

共 0 条评论 已展示 0 / 0 条评论
当前还没有评论,来发表第一条评论吧。若评论需要审核,通过后会展示在这里。