正文内容
简介
输出/输入


变量/常量


关键字
| 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 | true 或 false |
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;
}
- 包含占位形参的函数使用时必须传入对应类型的数据,否则会报错

- 占位参数还可以有默认值,此时则不必传入对应类型的数据
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个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
如果用户定义有参构造函数,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 子类 : 继承方式 父类
继承方式一共有三种:
公共继承
保护继承
私有继承
继承中的对象模型
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
多继承可能会出现多个父类中存在同名成员的情况,需要加作用域来区分。
菱形继承
菱形继承概念:
两个派生类继承同一个基类
又有某个类同时继承这两个派生类
这种继承被称为菱形继承,或者钻石继承
菱形继承问题:
羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性。
羊驼继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
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> 和 。
文件类型分为两种:
- 文本文件:文件以文本的ASCII码形式存储在计算机中
- 二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们
操作文件的三大类:
| 数据类型 | 描述 |
|---|---|
| ofstream | 该数据类型表示输出文件流,用于创建文件并向文件写入信息。 |
| ifstream | 该数据类型表示输入文件流,用于从文件读取信息。 |
| fstream | 该数据类型通常表示文件流,且同时具有 ofstream 和 ifstream 两种功能,这意味着它可以创建文件,向文件写入信息,从文件读取信息。 |
打开文件
在从文件读取信息或者向文件写入信息之前,必须先打开文件。ofstream 和 fstream 对象都可以用来打开文件进行写操作,如果只需要打开文件进行读操作,则使用 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++ 编程中,我们使用流插入运算符( << )向文件写入信息,就像使用该运算符输出信息到屏幕上一样。唯一不同的是,在这里您使用的是 ofstream 或 fstream 对象,而不是 cout 对象。
读取文件
在 C++ 编程中,我们使用流提取运算符( >> )从文件读取信息,就像使用该运算符从键盘输入信息一样。唯一不同的是,在这里您使用的是 ifstream 或 fstream 对象,而不是 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() 函数会忽略掉之前读语句留下的多余字符。
文件位置指针
istream 和 ostream 都提供了用于重新定位文件位置指针的成员函数。这些成员函数包括关于 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 );
正文内容已启用复制保护,代码块与行内代码仍支持复制。
读者留言
还没有评论,来发表第一条评论吧。