粗略重温C++

目录

C++简介

C++的特点:

  1. 静态类型的
  2. 编译式的
  3. 通用的
  4. 大小写敏感的
  5. 不规则的编程语言
  6. 支持过程化编程
  7. 面向对象编程
  8. 泛型编程
  9. 为中级语言,综合了高级语言和低级语言的特点

面向对象

包括面向对象开发的四大特性:

  1. 封装
    1. 封装是将数据和方法组合在一起,对外部隐藏实现细节只公开对外提供的接口
    2. 这样可以提高安全性、可靠性和灵活性。
  2. 继承
    1. 继承是从已有类中派生出新类,新类具有已有类的属性方法,并且可以扩展或修改这些属性方法
    2. 这样可以提高代码的复用性和可扩展性。
  3. 多态
    1. 多态是指同一种操作作用于不同的对象,可以有不同的解释和实现
  4. 抽象
    1. 抽象是从具体的实例中提取共同的特征,形成抽象类或接口,以便于代码的复用和扩展。抽象类和接口可以让程序员专注于高层次的设计和业务逻辑,而不必关注底层的实现细节。

标准库

标准的C++有三个重要部分组成:

  1. 核心语言库
    1. 提供了所有构件块,包括变量数据类型常量等等
  2. C++标准库
    1. 提供了大量的函数,用于操作文件、字符串
  3. 标准模板库(STL)
    1. 提供了大量的方法,用于操作数据结构

标准化

发布时间通称备注
2020C++20,C++2aISO/IEC 148882:2020
2017C++17第五个C++标准
2014C++14第四个C++标准
2011C++11第三个C++标准
2003C++03第二个C++标准

环境设置

略过

基本语法

C++ 程序可以定义为对象的集合,这些对象通过调用彼此的方法进行交互。现在让我们简要地看一下什么是类、对象,方法、即时变量

  1. 对象
    1. 对象具有状态和行为。
    2. 例如:一只狗的状态 - 颜色、名称、品种,行为 - 摇动、叫唤、吃。
    3. 对象是类的实例。
    1. 类可以定义为描述对象行为/状态的模板/蓝图。
  2. 方法
    1. 从基本上说,一个方法表示一种行为
    2. 一个类可包含多个方法。可在方法中写入逻辑操作数据以及执行所有的动作
  3. 即时变量
    1. 每个对象都有其独特的即时变量。对象的状态是由这些即时变量的值创建的。

C++ 不以行末作为结束符的标识,因此,您可以在一行上放置多个语句。例如:

x = y;
y = y+1;
add(x, y);
//或者如下表示
x = y; y = y+1; add(x, y);

注释

  • // - 一般用于单行注释
  • /*** … */** - 一般用于多行注释

数据类型

基本的内置类型

image-20240915221255706

wchar_t的来历很简单:

typedef short int wchar_t;

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

  1. signed
  2. unsigned
  3. short
  4. long

typedef 声明

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

typedef type newname; 
typedef int feet;

枚举类型

枚举类型(enumeration)是C++中的派生数据类型,它是由用户定义的若干枚举常量的集合。

如果一个变量只有几种可能的值,可以定义为枚举(enumeration)类型。

所谓"枚举"是指将变量的值一一列举出来,变量的值只能在列举出来的值的范围内。

创建枚举,需要使用关键字 enum。枚举类型的一般形式为:

enum 枚举名{ 
     标识符[=整型常数], 
     标识符[=整型常数], 
... 
    标识符[=整型常数]
} 枚举变量;
    

类型转换

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

静态转换(Static Cast)

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

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

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

int i = 10;
float f = static_cast<float>(i);

[!NOTE]

static_cast 用于将从一种数据类型强制转换到另一种数据类型

动态转换(Dynamic Cast)

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

动态转换在运行时进行类型检查,如果不能进行转换则返回空指针或引发异常。

class Base{};
class Derived : public Base{};
Base* ptr_base = new Derived;
Derived* ptr_derived = dynamic_cast<Derived*>(ptr_base);

[!NOTE]

Base是基类,Derived是派生类,需求:将一个基类指针转换为一个派生类指针

常量转换(Const Cast)

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

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

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

[!NOTE]

不是吧,哥们儿,将常量转换成非常量,要这么麻烦?

重新解释转换(Reinterpret Cast)

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

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

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

[!NOTE]

reinterpret_cast与static_cast有什么区别?

变量类型

在 C++ 中,有多种变量类型可用于存储不同种类的数据

C++ 中每个变量都有指定的类型类型决定了变量存储的大小和布局,该范围内的值都可以存储在内存中,运算符可应用于变量上。

image-20240915224620372

C++ 也允许定义各种其他类型的变量,比如枚举、指针、数组、引用、数据结构、类等等,这将会在后续的章节中进行讲解。

变量作用域

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

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

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

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

初始化局部变量和全局变量

image-20240915225555235

块作用域变量

#include <iostream>

int main() {
    int a = 10;
    {
        int a = 20;  // 块作用域变量
        std::cout << "块变量: " << a << std::endl;
    }
    std::cout << "外部变量: " << a << std::endl;
    return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:块变量: 20 外部变量: 10

类作用域变量

class Myclass{
    public:
    		static int class_var;
}

int Myclass::class_var = 30;

以上实例中,MyClass 类中声明了一个名为 class_var 的类作用域变量。可以使用类名和作用域解析运算符 :: 来访问这个变量。在 main() 函数中访问 class_var 时输出的是 30。

常量

常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量

常量可以是任何的基本数据类型,可分为整型数字、浮点数字、字符、字符串和布尔值

常量就像是常规的变量,只不过常量的值在定义后不能进行修改。

整数常量

整数常量可以是十进制、八进制或十六进制的常量。

前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制。

整数常量也可以带一个后缀,后缀是 U 和 L 的组合,U 表示无符号整数(unsigned),L 表示长整数(long)。后缀可以是大写,也可以是小写,U 和 L 的顺序任意。

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

浮点常量

浮点常量由整数部分小数点小数部分指数部分组成。

可以使用小数形式或者指数形式来表示浮点常量。

当使用小数形式表示时,必须包含整数部分、小数部分,或同时包含两者。

当使用指数形式表示时, 必须包含小数点、指数,或同时包含两者。

带符号的指数用 e 或 E 引入的。

下面列举几个浮点常量的实例:

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

修饰符类型

C++ 允许在 char、int 和 double 数据类型前放置修饰符。

修饰符是用于改变变量类型的行为的关键字,它更能满足各种情境的需求。

下面列出了数据类型修饰符:

  1. signed:
    1. 表示变量可以存储负数。
    2. 对于整型变量来说,signed 可以省略,因为整型变量默认为有符号类型
  2. unsigned:
    1. 表示变量不能存储负数。
    2. 对于整型变量来说,unsigned 可以将变量范围扩大一倍。
  3. short:
    1. 表示变量的范围比 int 更小。
    2. short int 可以缩写为 short。
  4. long:
    1. 表示变量的范围比 int 更大。
    2. long int 可以缩写为 long。
  5. long long:
    1. 表示变量的范围比 long 更大。
    2. C++11 中新增的数据类型修饰符。
  6. float:
    1. 表示单精度浮点数。
  7. double:
    1. 表示双精度浮点数。
  8. bool:
    1. 表示布尔类型,只有 true 和 false 两个值。
  9. char:
    1. 表示字符类型。
  10. wchar_t:
    1. 表示宽字符类型,可以存储 Unicode 字符。

修饰符 signed、unsigned、long 和 short 可应用于整型,signedunsigned 可应用于字符型,long 可应用于双精度型。

这些修饰符也可以组合使用,修饰符 signedunsigned 也可以作为 longshort 修饰符的前缀。例如:unsigned long int

类型限定符

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

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

存储类

存储类定义 C++ 程序中变量/函数的范围(可见性)和生命周期。

这些说明符放置在它们所修饰的类型之前。下面列出 C++ 程序中可用的存储类:

  1. auto

    1. 这是默认的存储类说明符,通常可以省略不写。
    2. auto 指定的变量具有自动存储期,即它们的生命周期仅限于定义它们的块(block)。
    3. auto 变量通常在栈上分配。
  2. register

    1. 用于建议编译器将变量存储在CPU寄存器中以提高访问速度。
    2. 在 C++11 及以后的版本中,register 已经是一个废弃的特性,不再具有实际作用。
  3. static

    1. 用于定义具有静态存储期的变量或函数,它们的生命周期贯穿整个程序的运行期。在函数内部,static变量的值在函数调用之间保持不变。在文件内部或全局作用域,static变量具有内部链接,只能在定义它们的文件中访问。
  4. extern

    1. 用于声明具有外部链接的变量或函数,它们可以在多个文件之间共享。
    2. 默认情况下,全局变量和函数具有 extern 存储类。在一个文件中使用extern声明另一个文件中定义的全局变量或函数,可以实现跨文件共享。
  5. mutable

    1. 用于修饰类中的成员变量,允许在const成员函数中修改这些变量的值。
    2. 通常用于缓存或计数器等需要在const上下文中修改的数据。
  6. thread_local

    1. 用于定义具有线程局部存储期的变量,每个线程都有自己的独立副本。
    2. 线程局部变量的生命周期线程的生命周期相同。

运算符

循环

循环类型

常用循环:

  1. while循环
  2. for循环
  3. do…while循环
  4. 嵌套循环

循环控制语句

循环控制语句更改执行的正常序列。

当执行离开一个范围时,所有在该范围中创建的自动对象都会被销毁。

  1. break语句
    1. 中止loop或者swith语句,程序流将继续执行紧接着loop或者switch的下一条语句
  2. continue语句
    1. 引起循环跳过主体的剩余部分,立即重新开始测试条件。
  3. goto 语句
    1. 将控制转移到被标记的语句。但是不建议在程序中使用 goto 语句。

无限循环

​ 如果条件永远不为假,则循环将变成无限循环。for 循环在传统意义上可用于实现无限循环。由于构成循环的三个表达式中任何一个都不是必需的,您可以将某些条件表达式留空来构成一个无限循环。

判断

函数

函数声明

创建 C++ 函数时,会定义函数做什么,然后通过调用函数来完成已定义的任务

函数参数

如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数

形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。

当调用函数时,有三种向函数传递参数的方式:

调用类型描述
传值调用该方法把参数的实际值赋值给函数的形式参数。
在这种情况下,修改函数内的形式参数对实际参数没有影响。
指针调用该方法把参数的地址赋值给形式参数。
在函数内,该地址用于访问调用中要用到的实际参数。
这意味着,修改形式参数会影响实际参数
引用调用该方法把参数的引用赋值给形式参数。
在函数内,该引用用于访问调用中要用到的实际参数。
这意味着,修改形式参数会影响实际参数

默认情况下,C++ 使用传值调用来传递参数。一般来说,这意味着函数内的代码不能改变用于调用函数的参数。之前提到的实例,调用 max() 函数时,使用了相同的方法。

Lambda 函数与表达式

C++11 提供了对匿名函数的支持,称为 Lambda 函数(也叫 Lambda 表达式)。

Lambda 表达式把函数看作对象。Lambda 表达式可以像对象一样使用,比如可以将它们赋给变量作为参数传递,还可以像函数一样对其求值。

Lambda 表达式本质上与函数声明非常类似。

Lambda 表达式具体形式如下:

[capture](parameters)->return-type{body}
[](int x, int y){ return x < y ; }

如果没有返回值可以表示为:

[capture](parameters){body}
[]{ ++global_x; } 

在一个更为复杂的例子中,返回类型可以被明确的指定如下:

[](int x, int y) -> int { int z = x + y; return z + x; }

数字

通常,当我们需要用到数字时,我们会使用原始的数据类型,如 int、short、long、float 和 double 等等。这些用于数字的数据类型,其可能的值和数值范围,我们已经在 C++ 数据类型一章中讨论过。

#include <iostream>
using namespace std;
 
int main ()
{
   // 数字定义
   short  s;
   int    i;
   long   l;
   float  f;
   double d;
   
   // 数字赋值
   s = 10;      
   i = 1000;    
   l = 1000000; 
   f = 230.47;  
   d = 30949.374;
   
   // 数字输出
   cout << "short  s :" << s << endl;
   cout << "int    i :" << i << endl;
   cout << "long   l :" << l << endl;
   cout << "float  f :" << f << endl;
   cout << "double d :" << d << endl;
 
   return 0;
}

数学运算

​ 在 C++ 中,除了可以创建各种函数,还包含了各种有用的函数供您使用。这些函数写在标准 C 和 C++ 库中,叫做内置函数。您可以在程序中引用这些函数。

​ C++ 内置了丰富的数学函数,可对各种数字进行运算。下表列出了 C++ 中一些有用的内置的数学函数。

为了利用这些函数,您需要引用数学头文件 <cmath>

1double cos(double);
2double pow(double, double);
假设第一个参数为 x,第二个参数为 y,则该函数返回 x 的 y 次方
3double hypot(double, double);
该函数返回两个参数的平方总和的平方根,也就是说,参数为一个直角三角形的两个直角边,函数会返回斜边的长度。
4double sqrt(double);
该函数返回参数的平方根。
5int abs(int);
该函数返回整数的绝对值。
6double fabs(double);
该函数返回任意一个浮点数的绝对值
7double floor(double);
该函数返回一个小于或等于传入参数的最大整数。

C++ 随机数

在许多情况下,需要生成随机数。

关于随机数生成器,有两个相关的函数。

一个是 rand(),该函数只返回一个伪随机数。生成随机数之前必须先调用 srand() 函数。

下面是一个关于生成随机数的简单实例。实例中使用了 time() 函数来获取系统时间的秒数,通过调用 rand() 函数来生成随机数

#include <iostream>
#include <ctime>
#include <cstdlib>
 
using namespace std;
 
int main ()
{
   int i,j;
 
   // 设置种子
   srand( (unsigned)time( NULL ) );
 
   /* 生成 10 个随机数 */
   for( i = 0; i < 10; i++ )
   {
      // 生成实际的随机数
      j= rand();
      cout <<"随机数: " << j << endl;
   }
 
   return 0;
}

数组

字符串

指针

学习 C++ 的指针既简单又有趣。通过指针,可以简化一些 C++ 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。所以,想要成为一名优秀的 C++ 程序员,学习指针是很有必要的

正如您所知道的,每一个变量都有一个内存位置每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。

请看下面的实例,它将输出定义的变量地址:

#include <iostream>
 
using namespace std;
 
int main ()
{
   int  var1;
   char var2[10];
 
   cout << "var1 变量的地址: ";
   cout << &var1 << endl;
 
   cout << "var2 变量的地址: ";
   cout << &var2 << endl;
 
   return 0;
}

通过上面的实例,了解了什么是内存地址以及如何访问它。接下来让我们看看什么是指针。

什么是指针?

指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址

就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明

指针变量声明的一般形式为:

type *var-name;

在这里,type 是指针的基类型,它必须是一个有效的 C++ 数据类型,var-name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明:

int    *ip;    /* 一个整型的指针 */
double *dp;    /* 一个 double 型的指针 */
float  *fp;    /* 一个浮点型的指针 */
char   *ch;    /* 一个字符型的指针 */

所有指针的值的实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同

C++中使用指针

使用指针时会频繁进行以下几个操作:

  1. 定义一个指针变量
  2. 把变量地址赋值给指针
  3. 访问指针变量中可用地址的值

​ 这些是通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值。下面的实例涉及到了这些操作:

#include <iostream>
 
using namespace std;
 
int main ()
{
   int  var = 20;   // 实际变量的声明
   int  *ip;        // 指针变量的声明
 
   ip = &var;       // 在指针变量中存储 var 的地址
 
   cout << "Value of var variable: ";
   cout << var << endl;
 
   // 输出在指针变量中存储的地址
   cout << "Address stored in ip variable: ";
   cout << ip << endl;
 
   // 访问指针中地址的值
   cout << "Value of *ip variable: ";
   cout << *ip << endl;
 
   return 0;
}

引用

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

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

C++ 引用 vs 指针

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

  1. 不存在空引用
    1. 引用必须连接到一块合法的内存。
  2. 一旦引用被初始化为一个对象,就不能被指向到另一个对象
    1. 指针可以在任何时候指向到另一个对象。
  3. 引用必须在创建时被初始化
    1. 指针可以在任何时间被初始化。

C++ 中创建引用

​ 试想变量名称是变量附属在内存位置中的标签,您可以把引用当成是变量附属在内存位置中的第二个标签。因此,您可以通过原始变量名称或引用来访问变量的内容。例如:

int i = 17;
int&  r = i;
double& s = d;

​ 在这些声明中,& 读作引用。因此,第一个声明可以读作 “r 是一个初始化为 i 的整型引用”,第二个声明可以读作 “s 是一个初始化为 d 的 double 型引用”。

下面的实例使用了 int 和 double 引用:

#include <iostream>
 
using namespace std;
 
int main ()
{
   // 声明简单的变量
   int    i;
   double d;
 
   // 声明引用变量
   int&    r = i;
   double& s = d;
   
   i = 5;
   cout << "Value of i : " << i << endl;
   cout << "Value of i reference : " << r  << endl;
 
   d = 11.7;
   cout << "Value of d : " << d << endl;
   cout << "Value of d reference : " << s  << endl;
   
   return 0;
}

日期/时间

​ C++ 标准库没有提供所谓的日期类型。C++ 继承了 C 语言用于日期和时间操作结构和函数。为使用日期和时间相关的函数和结构,需要在 C++ 程序中引用 <ctime> 头文件。

​ 有四个与时间相关的类型clock_ttime_tsize_ttm

类型 clock_t、size_t 和 time_t 能够把系统时间和日期表示为某种整数

结构类型 tm 把日期和时间C 结构的形式保存,tm 结构的定义如下:

struct tm {
  int tm_sec;   // 秒,正常范围从 0 到 59,但允许至 61
  int tm_min;   // 分,范围从 0 到 59
  int tm_hour;  // 小时,范围从 0 到 23
  int tm_mday;  // 一月中的第几天,范围从 1 到 31
  int tm_mon;   // 月,范围从 0 到 11
  int tm_year;  // 自 1900 年起的年数
  int tm_wday;  // 一周中的第几天,范围从 0 到 6,从星期日算起
  int tm_yday;  // 一年中的第几天,范围从 0 到 365,从 1 月 1 日算起
  int tm_isdst; // 夏令时
};

​ 下面是 C/C++ 中关于日期和时间的重要函数。所有这些函数都是 C/C++ 标准库的组成部分,您可以在 C++ 标准库中查看一下各个函数的细节。

  1. *time_t time(time_t time);

    1. 该函数返回系统的当前日历时间,自 1970 年 1 月 1 日以来经过的秒数。
    2. 如果系统没有时间,则返回 -1。
  2. **char ctime(const time_t time);

    1. 该返回一个表示当地时间的字符串指针
    2. 字符串形式 day month year hours:minutes:seconds year\n\0。
  3. **struct tm localtime(const time_t time);

    1. 该函数返回一个指向表示本地时间的 tm 结构的指针。
  4. char * asctime ( const struct tm * time );

    1. 该函数返回一个指向字符串的指针,字符串包含了 time 所指向结构中存储的信息
    2. 返回形式为:day month date hours:minutes:seconds year\n\0。
  5. **struct tm gmtime(const time_t time);

    1. 该函数返回一个指向 time 的指针,time 为 tm 结构,用协调世界时(UTC)也被称为格林尼治标准时间(GMT)表示。
  6. *time_t mktime(struct tm time)

    1. 该函数返回日历时间,相当于 time 所指向结构中存储的时间。
  7. double difftime ( time_t time2, time_t time1 );

    1. 该函数返回 time1 和 time2 之间相差的秒数。
  8. size_t strftime();

    1. 该函数可用于格式化日期和时间为指定的格式。

当前日期和时间

下面的实例获取当前系统的日期和时间,包括本地时间协调世界时(UTC)。

#include <iostream>
#include <ctime>
 
using namespace std;
 
int main( )
{
   // 基于当前系统的当前日期/时间
   time_t now = time(0);
   
   // 把 now 转换为字符串形式
   char* dt = ctime(&now);
 
   cout << "本地日期和时间:" << dt << endl;
 
   // 把 now 转换为 tm 结构
   tm *gmtm = gmtime(&now);
   dt = asctime(gmtm);
   cout << "UTC 日期和时间:"<< dt << endl;
}
OUT:
本地日期和时间:Sat Jan  8 20:07:41 2011
UTC 日期和时间:Sun Jan  9 03:07:41 2011

用结构 tm 格式化时间

tm 结构在 C/C++ 中处理日期和时间相关的操作时,显得尤为重要。

tm 结构以 C 结构的形式保存日期和时间。大多数与时间相关的函数都使用了 tm 结构

下面的实例使用了 tm 结构各种与日期和时间相关的函数

在练习使用结构之前,需要对 C 结构有基本的了解,并懂得如何使用箭头 -> 运算符来访问结构成员。

#include <iostream>
#include <ctime>
 
using namespace std;
 
int main( )
{
   // 基于当前系统的当前日期/时间
   time_t now = time(0);
 
   cout << "1970 到目前经过秒数:" << now << endl;
 
   tm *ltm = localtime(&now);
 
   // 输出 tm 结构的各个组成部分
   cout << "年: "<< 1900 + ltm->tm_year << endl;
   cout << "月: "<< 1 + ltm->tm_mon<< endl;
   cout << "日: "<<  ltm->tm_mday << endl;
   cout << "时间: "<< ltm->tm_hour << ":";
   cout << ltm->tm_min << ":";
   cout << ltm->tm_sec << endl;
}
1970 到目前时间:1503564157
年: 2017
月: 8
日: 24
时间: 16:42:37

基本的输入输出

C++ 标准库提供了一组丰富的输入/输出功能,我们将在后续的章节进行介绍。

本章将讨论 C++ 编程中最基本和最常见的 I/O 操作。

C++ 的 I/O 发生在流中,流是字节序列

如果字节流是从设备(如键盘、磁盘驱动器、网络连接等)流向内存,这叫做输入操作

如果字节流是从内存流向设备(如显示屏、打印机、磁盘驱动器、网络连接等),这叫做输出操作。

I/O 库头文件

头文件函数和描述
<iostream>该文件定义了 cincoutcerrclog 对象分别对应于
标准输入流标准输出流非缓冲标准错误流缓冲标准错误流
<iomanip>该文件通过所谓的参数化的流操纵器(比如 setwsetprecision
来声明对执行标准化 I/O 有用的服务。
<fstream>该文件为用户控制的文件处理声明服务
我们将在文件和流的相关章节讨论它的细节

数据结构

​ C/C++ 数组允许定义可存储相同类型数据项的变量,但是结构是 C++ 中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。

​ 结构用于表示一条记录,假设您想要跟踪图书馆中书本的动态,您可能需要跟踪每本书的下列属性:

定义结构

​ 为了定义结构,您必须使用 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;

访问结构成员

为了访问结构的成员,我们使用成员访问运算符(.)

成员访问运算符结构变量名称和我们要访问的结构成员之间的一个句号。

下面的实例演示了结构的用法:

#include <iostream>
#include <cstring>
 
using namespace std;
 
// 声明一个结构体类型 Books 
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};
 
int main( )
{
   Books Book1;        // 定义结构体类型 Books 的变量 Book1
   Books Book2;        // 定义结构体类型 Books 的变量 Book2
 
   // Book1 详述
   strcpy( Book1.title, "C++ 教程");
   strcpy( Book1.author, "Runoob"); 
   strcpy( Book1.subject, "编程语言");
   Book1.book_id = 12345;
 
   // Book2 详述
   strcpy( Book2.title, "CSS 教程");
   strcpy( Book2.author, "Runoob");
   strcpy( Book2.subject, "前端技术");
   Book2.book_id = 12346;
 
   // 输出 Book1 信息
   cout << "第一本书标题 : " << Book1.title <<endl;
   cout << "第一本书作者 : " << Book1.author <<endl;
   cout << "第一本书类目 : " << Book1.subject <<endl;
   cout << "第一本书 ID : " << Book1.book_id <<endl;
 
   // 输出 Book2 信息
   cout << "第二本书标题 : " << Book2.title <<endl;
   cout << "第二本书作者 : " << Book2.author <<endl;
   cout << "第二本书类目 : " << Book2.subject <<endl;
   cout << "第二本书 ID : " << Book2.book_id <<endl;
 
   return 0;
}

结构作为函数参数

您可以把结构作为函数参数传参方式与其他类型的变量或指针类似

您可以使用上面实例中的方式来访问结构变量:

#include <iostream>
#include <cstring>
using namespace std;
void printBook( struct Books book );
// 声明一个结构体类型 Books 
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};
int main( )
{
   Books Book1;        // 定义结构体类型 Books 的变量 Book1
   Books Book2;        // 定义结构体类型 Books 的变量 Book2
 
    // Book1 详述
   strcpy( Book1.title, "C++ 教程");
   strcpy( Book1.author, "Runoob"); 
   strcpy( Book1.subject, "编程语言");
   Book1.book_id = 12345;
 
   // Book2 详述
   strcpy( Book2.title, "CSS 教程");
   strcpy( Book2.author, "Runoob");
   strcpy( Book2.subject, "前端技术");
   Book2.book_id = 12346;
 
   // 输出 Book1 信息
   printBook( Book1 );
 
   // 输出 Book2 信息
   printBook( Book2 );
 
   return 0;
}
void printBook( struct Books book )
{
   cout << "书标题 : " << book.title <<endl;
   cout << "书作者 : " << book.author <<endl;
   cout << "书类目 : " << book.subject <<endl;
   cout << "书 ID : " << book.book_id <<endl;
}

指向结构的指针

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

struct Books *struct_pointer;

现在,您可以在上述定义的指针变量存储结构变量的地址

为查找结构变量的地址,请把 & 运算符放在结构名称的前面,如下所示:

struct_pointer = &Book1;

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

struct_pointer->title;

让我们使用结构指针来重写上面的实例,这将有助于您理解结构指针的概念:

#include <iostream>
#include <cstring>
 
using namespace std;
void printBook( struct Books *book );
 
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};
 
int main( )
{
   Books Book1;        // 定义结构体类型 Books 的变量 Book1
   Books Book2;        // 定义结构体类型 Books 的变量 Book2
 
    // Book1 详述
   strcpy( Book1.title, "C++ 教程");
   strcpy( Book1.author, "Runoob"); 
   strcpy( Book1.subject, "编程语言");
   Book1.book_id = 12345;
 
   // Book2 详述
   strcpy( Book2.title, "CSS 教程");
   strcpy( Book2.author, "Runoob");
   strcpy( Book2.subject, "前端技术");
   Book2.book_id = 12346;
 
   // 通过传 Book1 的地址来输出 Book1 信息
   printBook( &Book1 );
 
   // 通过传 Book2 的地址来输出 Book2 信息
   printBook( &Book2 );
 
   return 0;
}
// 该函数以结构指针作为参数
void printBook( struct Books *book )
{
   cout << "书标题  : " << book->title <<endl;
   cout << "书作者 : " << book->author <<endl;
   cout << "书类目 : " << book->subject <<endl;
   cout << "书 ID : " << book->book_id <<endl;
}

vector容器

C++ 中的 vector 是一种序列容器,它允许你在运行时动态地插入和删除元素

vector 是基于数组的数据结构,但它可以自动管理内存,这意味着你不需要手动分配和释放内存。

与 C++ 数组相比,vector 具有更多的灵活性和功能,使其成为 C++ 中常用数据结构之一。

vector 是 C++ 标准模板库(STL)的一部分,提供了灵活的接口和高效的操作。

C++面向对象

类/对象

继承

重载运算符或重载函数

多态

数据抽象

数据封装

接口(抽象类)

C++高级教程

模板

模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码

模板是创建泛型类或函数的蓝图或公式

库容器,比如迭代器算法,都是泛型编程的例子,它们都使用了模板的概念。

每个容器都有一个单一的定义,比如 向量,我们可以定义许多不同类型的向量,比如 vector <int> 或 vector <string>。

您可以使用模板来定义函数和类,接下来让我们一起来看看如何使用。

函数模板

模板函数定义的一般形式如下所示:

template <typename type> ret-type func-name(parameter list)
{
   // 函数的主体
}

在这里,type 是函数所使用的数据类型的占位符名称。这个名称可以在函数定义中使用。

下面是函数模板的实例,返回两个数中的最大值

#include <iostream>
#include <string>
 
using namespace std;
 
template <typename T>
inline T const& Max (T const& a, T const& b) 
{ 
    return a < b ? b:a; 
} 
int main ()
{
 
    int i = 39;
    int j = 20;
    cout << "Max(i, j): " << Max(i, j) << endl; 
 
    double f1 = 13.5; 
    double f2 = 20.7; 
    cout << "Max(f1, f2): " << Max(f1, f2) << endl; 
 
    string s1 = "Hello"; 
    string s2 = "World"; 
    cout << "Max(s1, s2): " << Max(s1, s2) << endl; 
 
    return 0;
}

类模板

正如我们定义函数模板一样,我们也可以定义类模板。泛型类声明的一般形式如下所示:

template <class type> class class-name {
.
.
.
}

在这里,type 是占位符类型名称,可以在类被实例化的时候进行指定

您可以使用一个逗号分隔的列表来定义多个泛型数据类型。

下面的实例定义了类 Stack<>,并实现了泛型方法来对元素进行入栈出栈操作:

#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>
 
using namespace std;
 
template <class T>
class Stack { 
  private: 
    vector<T> elems;     // 元素 
 
  public: 
    void push(T const&);  // 入栈
    void pop();               // 出栈
    T top() const;            // 返回栈顶元素
    bool empty() const{       // 如果为空则返回真。
        return elems.empty(); 
    } 
}; 
 
template <class T>
void Stack<T>::push (T const& elem) 
{ 
    // 追加传入元素的副本
    elems.push_back(elem);    
} 
 
template <class T>
void Stack<T>::pop () 
{ 
    if (elems.empty()) { 
        throw out_of_range("Stack<>::pop(): empty stack"); 
    }
    // 删除最后一个元素
    elems.pop_back();         
} 
 
template <class T>
T Stack<T>::top () const 
{ 
    if (elems.empty()) { 
        throw out_of_range("Stack<>::top(): empty stack"); 
    }
    // 返回最后一个元素的副本 
    return elems.back();      
} 
 
int main() 
{ 
    try { 
        Stack<int>    intStack;  // int 类型的栈 
        Stack<string> stringStack;    // string 类型的栈 
 
        // 操作 int 类型的栈 
        intStack.push(7); 
        cout << intStack.top() <<endl; 
 
        // 操作 string 类型的栈 
        stringStack.push("hello"); 
        cout << stringStack.top() << std::endl; 
        stringStack.pop(); 
        stringStack.pop(); 
    } 
    catch (exception const& ex) { 
        cerr << "Exception: " << ex.what() <<endl; 
        return -1;
    } 
}

[!NOTE]

9.17 结束C++复习至image-20240917195704150

后面就用到什么看什么

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

后厂村路小狗蛋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值