C++语法基础

文章目录

  • 一、基础语法
    • 1.1 C++ 简介
      • 面向对象程序设计
      • 标准库
      • ANSI 标准
    • 1.2 C++ 环境设置
      • 文本编辑器
      • 编译器
    • 1.3 C++ 基本语法
      • C++ 程序结构
      • 编译&执行 C++ 程序
      • C++ 中的分号 & 语句块
    • 1.4 C++ 注释
    • 1.5 C++ 数据类型
      • 基本内置类型
      • typedef 声明
      • 枚举类型
    • 1.6 C++ 变量类型
      • C++ 中的变量声明
      • C++ 中的左值(Lvalues)和右值(Rvalues)
    • 1.7 C++ 变量作用域
    • 1.8 C++ 常量
      • 定义常量
    • 1.9 C++ 修饰符类型
      • 类型限定符
    • 1.10 C++ 存储类
      • auto
      • register
      • static
      • extern
      • mutable
      • thread_local
    • 1.11 C++ 运算符
    • 1.12 C++ 循环
      • 循环类型
    • 1.13 C++ 判断
      • 嵌套语句
      • ? : 运算符
    • 1.14 C++函数
  • 无返回值:
  • eg:
  • 返回值类型可以明确指定:
    • 1.15 C++ 数字
      • 多维数组
      • 指向数组的指针
      • 从函数返回数组
    • 1.17 C++ 字符串
      • C++ 中的 String 类
    • 1.18 C++ 指针
      • 指针的算数运算
      • 指针数组
      • 传递指针给函数
      • 传递指针给函数
      • 从函数返回指针
    • 1.19 C++ 引用
      • 引用 vs 指针
      • 创建引用
      • 引用作为参数
      • 引用作为返回值
    • 1.20 C++ 日期 & 时间
      • 标准输出流(cout)
      • 标准输入流(cin)
      • 标准错误流(cerr)
      • 标准日志流(clog)
    • 1.22 C++ 结构体

一、基础语法

1.1 C++ 简介

  • C++ 是一种静态类型的、编译式的、通用的、大小写敏感的、不规则的编程语言,支持过程化编程、面向对象编程和泛型编程。- C++ 是 C 的一个超集,事实上,任何合法的 C 程序都是合法的 C++ 程序。- 使用静态类型的编程语言是在编译时执行类型检查,而不是在运行时执行类型检查。

面向对象程序设计

  • 完全支持面向对象的程序设计,包括面向对象开发的四大特性:封装、抽象、继承、多态。

标准库

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

ANSI 标准

  • 所有的C++编译器制造商均支持ANSI 标准,能够保证其便携性(代码在Mac、UNIX、Windows、Alpha 计算机上都能通过编译)。

1.2 C++ 环境设置

文本编辑器

编译器

  • 写在源文件中的源代码是人类可读的源。它需要"编译",转为机器语言,这样 CPU 可以按给定指令执行程序。- 大多数的 C++ 编译器并不在乎源文件的扩展名,但是如果您未指定扩展名,则默认使用 .cpp。- 常用的免费可用的编译器是 GNU 的 C/C++ 编译器

1.3 C++ 基本语法

C++ 程序可以定义为对象的集合,这些对象通过调用彼此的方法进行交互。

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

C++ 程序结构

  • Eg:输出“Hello World”
  #include <iostream>
  using namespace std;
  
  // main() 是程序开始执行的地方
  int main()
  {
     cout << "Hello World"; // 输出 Hello World
     return 0;
  }

  • using namespace std; 告诉编译器使用 std 命名空间。

编译&执行 C++ 程序

  • 上述代码保存为“hello.cpp”,打开所在路径,输出“Hello World”
$ g++ hello.cpp
$ ./a.out

C++ 中的分号 & 语句块

  • 在 C++ 中,分号是语句结束符。也就是说,每个语句必须以分号结束。它表明一个逻辑实体的结束。
  • 语句块是一组使用大括号括起来的按逻辑连接的语句。

1.4 C++ 注释

  • /*开始*/结束(多行)
  • // 单行

1.5 C++ 数据类型

基本内置类型

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

typedef 声明

  • 可以使用 typedef 为一个已有的类型取一个新的名字。typedef type newname;

枚举类型

  • 枚举类型(enumeration)是C++中的一种派生数据类型,它是由用户定义的若干枚举常量的集合。如果一个变量只有几种可能的值,可以定义为枚举(enumeration)类型。
  • 关键字 enum
enum 枚举名{ 
     标识符[=整型常数], 
     标识符[=整型常数], 
... 
    标识符[=整型常数]
} 枚举变量;

  • 如果枚举没有初始化, 即省掉"=整型常数"时, 则从第一个标识符开始。例如,下面的代码定义了一个颜色枚举,变量 c 的类型为 color。最后,c 被赋值为 “blue”。
enum color { red, green, blue } c;
c = blue;

  • 默认情况下,第一个名称的值为 0,第二个名称的值为 1,第三个名称的值为 2,以此类推。但是,您也可以给名称赋予一个特殊的值,只需要添加一个初始值即可。例如,在下面的枚举中,green 的值为 5。blue 的值为 6,因为默认情况下,每个名称都会比它前面一个名称大 1,但 red 的值依然为 0。
enum color { red, green=5, blue };

1.6 C++ 变量类型

  • 大写字母和小写字母是不同的,因为 C++ 是大小写敏感的。

C++ 中的变量声明

  • 使用extern声明变量,使用多个文件且只在其中一个文件中定义变量。
  • 变量可以声明多次,但只能定义一次。
  • extern作用:extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。此外extern也可用来进行链接指定。
  • 在文件t2.cpp中有代码:
int x = 10;

在文件t1.cpp中有代码:

#include <iostream>
using namespace std;

extern int x;
int main() {
    cout << x;

    return 0;
}

输出:10 extern表示声明一个变量,声明使得这个名字为程序所指,而定义创建了和这个名字相关联的实体。 如:

int x = 10;

则是定义一个变量。 声明和定义分开,这样的意义就是可以在一个文件中使用另一个文件的变量,如上面的t1.cpp使用t2.cpp的变量。

C++ 中的左值(Lvalues)和右值(Rvalues)

  • 左值(lvalue):指向内存位置的表达式被称为左值(lvalue)表达式。左值可以出现在赋值号的左边或右边。
  • 右值(rvalue):术语右值(rvalue)指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式,也就是说,右值可以出现在赋值号的右边,但不能出现在赋值号的左边。
  • 变量是左值,数值型的数字是右值(Eg:10)

1.7 C++ 变量作用域

  • 在函数或一个代码块内部声明的变量,称为局部变量。
  • 在函数参数的定义中声明的变量,称为形式参数。
  • 在所有函数外部声明的变量,称为全局变量。
  • 当局部变量被定义时,系统不会对其初始化,必须自行对其初始化。定义全局变量时,系统会自动初始化。

1.8 C++ 常量

  • 常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量。- 常量可以是任何的基本数据类型,可分为整型数字、浮点数字、字符、字符串和布尔值。- 常量就像是常规的变量,只不过常量的值在定义后不能进行修改。
  • 可以带一个后缀,后缀是 U 和 L 的组合,U 表示无符号整数(unsigned),L 表示长整数(long)

定义常量

  1. 使用 #define 预处理器。
#define identifier value
// eg:
#define LENGTH 10 
  1. 使用 const 关键字。
const type variable = value;
// eg:
const int  LENGTH = 10;
  1. 二者区别: 用const的优点:会进行类型安全检查。而define没有安全检查,且可能会产生意料不到的错误。

1.9 C++ 修饰符类型

  • C++ 允许在 char、int 和 double 数据类型前放置修饰符。- 数据类型修饰符:signed、unsigned、long、short
  • 修饰符 signed、unsigned、long 和 short 可应用于整型,signed 和 unsigned 可应用于字符型,long 可应用于双精度型。
  • 修饰符 signed 和 unsigned 也可以作为 long 或 short 修饰符的前缀。例如:unsigned long int。

类型限定符

  • volatile作用: volatile关键字是防止在共享的空间发生读取的错误。只保证其可见性,不保证原子性;使用volatile指每次从内存中读取数据,而不是从编译器优化后的缓存中读取数据,简单来讲就是防止编译器优化。

1.10 C++ 存储类

存储类定义 C++ 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前。

  • auto- register- static- extern
  • mutable- thread_local (C++11)
  • 从 C++ 11 开始,auto 关键字不再是 C++ 存储类说明符,且 register 关键字被弃用。

auto

  • 自 C++ 11 以来,auto 关键字用于两种情况:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。

register

  • register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)
  • Eg:register int miles;
  • 寄存器只用于需要快速访问的变量,比如计数器。还应注意的是,定义 ‘register’ 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制。

static

  • static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。(局部静态变量不销毁)
  • static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。

#include <iostream>

// 函数声明 
void func(void);

static int count = 10; /* 全局变量 */

int main()
{
    while(count--)
    {
       func();
    }
    return 0;
}
// 函数定义
void func( void )
{
    static int i = 5; // 局部静态变量
    i++;
    std::cout << "变量 i 为 " << i ;
    std::cout << " , 变量 count 为 " << count << std::endl;
}

结果:


变量 i 为 6 , 变量 count 为 9
变量 i 为 7 , 变量 count 为 8
变量 i 为 8 , 变量 count 为 7
变量 i 为 9 , 变量 count 为 6
变量 i 为 10 , 变量 count 为 5
变量 i 为 11 , 变量 count 为 4
变量 i 为 12 , 变量 count 为 3
变量 i 为 13 , 变量 count 为 2
变量 i 为 14 , 变量 count 为 1
变量 i 为 15 , 变量 count 为 0

extern

  • extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。
  • extern 是用来在另一个文件中声明一个全局变量或函数,通常用于当有两个或多个文件共享相同的全局变量或函数。

mutable

  • 允许对象的成员替代常量。也就是说,mutable 成员可以通过 const 成员函数修改。

thread_local

  • 使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。
  • 可以将 thread_local 仅应用于数据声明和定义,thread_local 不能用于函数声明或定义。

thread_local int x;  // 命名空间下的全局变量
class X
{
    static thread_local std::string s; // 类的static成员变量
};
static thread_local std::string X::s;  // X::s 是需要定义的

void foo()
{
    thread_local std::vector<int> v;  // 本地变量
}

  • thread_local变量是C++ 11新引入的一种存储类型。它会影响变量的存储周期(Storage duration),C++中有4种存储周期
  • automatic- static- dynamic- thread
  • 有且只有thread_local关键字修饰的变量具有线程周期(thread duration),这些变量(或者说对象)在线程开始的时候被生成(allocated),在线程结束的时候被销毁(deallocated)。并且每 一个线程都拥有一个独立的变量实例(Each thread has its own instance of the object)。
  • thread_local可以和staticextern关键字联合使用,这将影响变量的链接属性(to adjust linkage)。

适用范围:

  • 命名空间下的全局变量- 类的static成员变量- 本地变量
  • 既然每个线程都拥有一份独立的thread_local变量,那么就有2个问题需要考虑:
  • 各线程的thread_local变量是如何初始化的?

每个线程都会进行一次单独初始化1. 各线程的thread_local变量在初始化之后拥有怎样的生命周期,特别是被声明为thread_local的本地变量(local variables)?- 声明为thread_local的本地变量在线程中是持续存在的,不同于普通临时变量的生命周期,它具有static变量一样的初始化特征和生命周期

1.11 C++ 运算符

运算符是一种告诉编译器执行特定的数学或逻辑操作的符号

  • 算术运算符- 关系运算符- 逻辑运算符- 位运算符- 赋值运算符- 杂项运算符

1.12 C++ 循环

循环类型

您可以在 while、for 或 do…while 循环内使用一个或多个循环。

1.13 C++ 判断

嵌套语句

? : 运算符


A ? B : c;

A真则B,A假为C。

1.14 C++函数

  • 在函数声明中,参数的名称并不重要,只有参数的类型是必需的,因此第二条也是有效的声明:
    int max(int num1, int num2);
    
  • int max(int, int);
    - 在一个源文件中定义函数且在另一个文件中调用函数时,函数声明是必需的。应该在调用函数的文件顶部声明函数。

  • 函数参数:
    • - 函数使用参数,必须声明接受参数值的变量。这些变量称为函数的**形式参数**。 - 形式参数就像函数内的其他局部变量,在**进入函数时被创建,退出函数时被销毁**。
    • 当调用函数时,有三种向函数传递参数的方式:
      调用类型 描述
      默认方法,该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数对实际参数没有影响。 该方法把参数的地址复制给形式参数。在函数内,该地址用于访问调用中要用到的实际参数。这意味着,**修改形式参数会影响实际参数**。 该方法把参数的引用复制给形式参数。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,**修改形式参数会影响实际参数**。
    • 参数默认值 - 在函数定义中使用赋值运算符来为参数赋默认值。- 调用函数时,如果未传递参数的值,则会使用默认值,如果指定了值,则会忽略默认值,使用传递的值。 Lambda 函数与表达式 - C++11 提供了对匿名函数的支持,称为 Lambda 函数(也叫 Lambda 表达式)。 - Lambda 表达式把函数看作对象。Lambda 表达式可以像对象一样使用,比如可以将它们赋给变量和作为参数传递,还可以像函数一样对其求值。
    • 形式:
      [capture](parameters)->return-type{body}
      # eg:
      [](int x, int y){ return x < y ; }
      

    无返回值:

    capture{body}

    eg:

    []{ ++global_x; }

    返回值类型可以明确指定:

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

  • 在Lambda表达式内可以访问 当前作用域 的变量,这是Lambda表达式的闭包(Closure)行为。 与JavaScript闭包不同,C++变量传递有传值和传引用的区别。可以通过前面的[]来指定:
    []      // 沒有定义任何变量。使用未定义变量会引发错误。
    [x, &y] // x以传值方式传入(默认),y以引用方式传入。
    [&] // 任何被使用到的外部变量都隐式地以引用方式加以引用。
    [=] // 任何被使用到的外部变量都隐式地以传值方式加以引用。
    [&, x] // x显式地以传值方式加以引用。其余变量以引用方式加以引用。
    [=, &z] // z显式地以引用方式加以引用。其余变量以传值方式加以引用。
  • 对于[=]或[&]的形式,lambda 表达式可以直接使用 this 指针。但是,对于[]的形式,如果要使用 this 指针,必须显式传入:
    this { this->someFunc(); }();

  • 指针和引用的区别?

    • 指针是*,通过它能找到以它为地址的内存单元。- 引用是&,就是某个变量的别名,对引用操作与对变量直接操作完全一样。
    • 区别:
      • 引用必须要初始化。- 指针是个实体,而引用是别名。- 引用只能在定义时被初始化一次,之后不可变,指针可以改变所指的对象。- 可以有const指针,没有const引用。- 指针自加,是指向下一次地址,而引用自加:是本身值的增加。- 引用只能被初始化一次,不能再换成别的名字了。

      • 指针调用与引用调用的区别在于:
  • 指针变量存储的是一个指针,也就是一个存储地址,当使用指针作为形参的时候,可以改变指针的指向,访问该地址的数据时需要加上符号 * ;- 而引用调用他是直接将一个地址传递到函数内,相当于在该函数内给变量起了一个别名,不可改变指向,但可以改变该地址内所存储的值;
  • 1.15 C++ 数字

  • int rand(void) 与 void srand(unsigned int seed)
    • - 头文件:`#include`- 用户未设定随机数种子时,系统默认的随机数种子为1。rand()产生的是伪随机数字,每次执行时是相同的;若要不同,用函数srand()初始化它。- rand()和srand()要一起使用,其中srand()用来初始化随机数种子,rand()用来产生随机数。- 参数seed必须是个整数,通常可以利用time(0)的返回值或NULL来当做seed。如果每次seed都设相同值,rand()所产生的随机数值每次就会一样。(失去随机性)- 可以把 seed 设为 `time(0)` 保证随机性。- 要产生 0–n 内的随机数:`rand() % n`;a–b 内的随机数 `a + rand() % (b - a + 1)`;左闭右开 ## 1.16 C++ 数组 - 存储一个**固定大小**的**相同**类型元素的顺序集合 - 所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素 ### 声明数组
    • **arraySize** 必须是一个大于零的整数常量,**type** 可以是任意有效的 C++ 数据类型
      type arrayName [ arraySize ];
      
    • ### 初始化数组
    • 如果不指定数组大小,则为初始化时元素的个数。
      double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
      
  • double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};

    多维数组

    • type name[size1][size2]...[sizeN];
    • eg:初始化二维数组
      int a[3][4] = {
      {0, 1, 2, 3} , /* 初始化索引号为 0 的行 /
      {4, 5, 6, 7} , /
      初始化索引号为 1 的行 /
      {8, 9, 10, 11} /
      初始化索引号为 2 的行 */
      };

    // 等同于:
    int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};

    指向数组的指针

    • 数组名是一个指向数组中第一个元素的常量指针
    • balance 是一个指向 &balance[0] 的指针,即数组 balance 的第一个元素的地址。
      double balance[50];

    // 把 p 赋值为 balance 的第一个元素的地址

    double *p;
    double balance[10];

    p = balance;
    - *(balance + 4) 等同于 *(p+4) 即访问 balance[4] 的数据

    从函数返回数组

    • C++ 不允许返回一个完整的数组作为函数的参数;可以通过指定不带索引的数组名来返回一个指向数组的指针。
    • 想要从函数返回一个一维数组,您必须声明一个返回指针的函数,如下:
      int * myFunction()
      {
      .
      .
      .
      }
    • - C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量
    • eg:生成 10 个随机数,并使用数组来返回它们
    #include 
    #include 
    #include 
    
    using namespace std;
    
    // 要生成和返回随机数的函数
    int * getRandom( )
    {
      static int  r[10];
    
      // 设置种子
      srand( (unsigned)time( NULL ) );
      for (int i = 0; i < 10; ++i)
      {
        r[i] = rand();
        cout << r[i] << endl;
      }
    
      return r;
    }
    
    // 要调用上面定义函数的主函数
    int main ()
    {
       // 一个指向整数的指针
       int *p;
    
       p = getRandom();
       for ( int i = 0; i < 10; i++ )
       {
           cout << "*(p + " << i << ") : ";
           cout << *(p + i) << endl;
       }
    
       return 0;
    }
    

    1.17 C++ 字符串

  • C++ 提供了以下两种类型的字符串表示形式:
    • - C 风格字符串- C++ 引入的 string 类类型 ### C 风格字符串 - 字符串实际上是使用 **null** 字符 ‘\0’ 终止的一维字符数组。 - 字符数组的大小比实际的字符数多一个。 - C++ 编译器会在初始化数组时,自动把 ‘\0’ 放在字符串的末尾。
    • 初始化:
      char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
      
  • // 或者:

    char greeting[] = “Hello”;
    - 可用函数:

    序号函数 & 目的 |------ 1**strcpy(s1, s2);** 复制字符串 s2 到字符串 s1。 2**strcat(s1, s2);** 连接字符串 s2 到字符串 s1 的末尾。 3**strlen(s1);** 返回字符串 s1 的长度。(不包括 `"\0"` ) 4**strcmp(s1, s2);** 如果 s1 和 s2 是相同的,则返回 0;如果 s1s2 则返回值大于 0。 5**strchr(s1, ch);** 返回一个指针,指向字符串 s1 中 **字符** ch 的第一次出现的位置。 6**strstr(s1, s2);** 返回一个指针,指向字符串 s1 中 **字符串** s2 的第一次出现的位置。
  • 示例:
  • #include 
    #include 
    
    using namespace std;
    
    int main ()
    {
       char str1[11] = "Hello";
       char str2[11] = "World";
       char str3[11];
       int  len ;
    
       // 复制 str1 到 str3
       strcpy( str3, str1);
       cout << "strcpy( str3, str1) : " << str3 << endl;
    
       // 连接 str1 和 str2
       strcat( str1, str2);
       cout << "strcat( str1, str2): " << str1 << endl;
    
       // 连接后,str1 的总长度
       len = strlen(str1);
       cout << "strlen(str1) : " << len << endl;
    
       system("pause");
       return 0;
    }
    
    // strcpy( str3, str1) : Hello
    // strcat( str1, str2): HelloWorld
    // strlen(str1) : 10
    

    C++ 中的 String 类

    • C++ 标准库提供了 string 类类型,支持上述所有的操作,另外还增加了其他更多的功能。
    • 示例:
    #include 
    #include 
    
    using namespace std;
    
    int main ()
    {
       string str1 = "Hello";
       string str2 = "World";
       string str3;
       int  len ;
    
       // 复制 str1 到 str3
       str3 = str1;
       cout << "str3 : " << str3 << endl;
    
       // 连接 str1 和 str2
       str3 = str1 + str2;
       cout << "str1 + str2 : " << str3 << endl;
    
       // 连接后,str3 的总长度
       len = str3.size();
       cout << "str3.size() :  " << len << endl;
    
       system("pause");
       return 0;
    }
    
    // str3 : Hello
    // str1 + str2 : HelloWorld
    // str3.size() :  10
    

    1.18 C++ 指针

    • 每个变量都有一个内存地址,用 “&” 访问地址。 - 指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。声明:type *name; - 所有指针的值的实际数据类型(整型、浮点型、字符型等)都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同
    • 示例:
    #include 
    
    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;
    }
    
    // Value of var variable: 20
    // Address stored in ip variable: 0xbfc601ac
    // Value of *ip variable: 20
    
  • C++ 指针详解:
  • 概念 描述
    C++ 支持空指针。NULL 指针是一个定义在标准库中的值为零的常量。 可以对指针进行四种算术运算:++、–、+、- 指针和数组之间有着密切的关系。 可以定义用来存储指针的数组。 C++ 允许指向指针的指针。 通过引用或地址传递参数,使传递的参数在调用函数中被改变。 C++ 允许函数返回指针到局部变量、静态变量和动态内存分配。
    ### 空指针 - 变量声明时,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为**空**指针。
  • NULL 指针是一个定义在标准库中的值为零的常量:
  • #include 
    
    using namespace std;
    
    int main ()
    {
       int  *ptr = NULL;
    
       cout << "ptr 的值是 " << ptr ;
    
       return 0;
    }
    // ptr 的值是 0
    
    - 内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。 - 因此,如果所有未使用的指针都被赋予空值,同时避免使用空指针,就可以防止误用一个未初始化的指针。很多时候,未初始化的变量存有一些垃圾值,导致程序难以调试。 - 空指针尽可能的使用 `nullptr`

    指针的算数运算

    • 指针是一个用数值表示的地址。因此,可以对指针执行算术运算。 - 可以对指针进行四种算术运算:++(指向下一个变量的地址)、–(指向上一个变量的地址)、+、-。
    • 经常用指针代替数组,因为变量指针可以递增,而数组不能递增,因为数组是一个常量指针。示例:
    #include 
    
    using namespace std;
    const int MAX = 3;
    
    int main ()
    {
       int  var[MAX] = {10, 100, 200};
       int  *ptr;
    
       // 指针指向数组第一个元素地址
       ptr = var;
       // 指针指向数组最后一个地址
       // ptr = &var[MAX-1]
       for (int i = 0; i < MAX; i++)
       {
          cout << "Address of var[" << i << "] = ";
          cout << ptr << endl;
    
          cout << "Value of var[" << i << "] = ";
          cout << *ptr << endl;
    
          // 移动到下一个位置
          ptr++;
          // 移动到上一个位置
          ptr--;
       }
       return 0;
    }
    
    - 指针的比较: 指针可以用关系运算符进行比较,如 ==、< 和 >。如果 p1 和 p2 指向两个相关的变量,比如同一个数组中的不同元素,则可对 p1 和 p2 进行大小比较。

    指针数组

    • 让数组存储指向 int 或 char 或其他数据类型的指针 - 指向整数的指针数组的声明:int *ptr[MAX]; - 这里,把 ptr 声明为一个数组,由 MAX 个整数指针组成。因此,ptr 中的每个元素,都是一个指向 int 值的指针。
    • 示例:把三个整数存储在一个指针数组中
    #include 
    
    using namespace std;
    const int MAX = 3;
    
    int main ()
    {
       int  var[MAX] = {10, 100, 200};
       int *ptr[MAX];
    
       for (int i = 0; i < MAX; i++)
       {
          ptr[i] = &var[i]; // 赋值为整数的地址
       }
       for (int i = 0; i < MAX; i++)
       {
          cout << "Value of var[" << i << "] = ";
          cout << *ptr[i] << endl;
       }
       return 0;
    }
    // Value of var[0] = 10
    // Value of var[1] = 100
    // Value of var[2] = 200
    
  • 指针数组 与 数组指针
    • - int *p[n]为指针数组:即存放指针的数组,数组中有n个元素,每个元素为一个int型的指针;- int (*p)[n]为数组指针:即指向数组的指针,p为一个指针,指向一个包含n个int元素的数组。- 数组和指针,在后面的为主语,前面的是定语,用来修饰主语。 ### 指向指针的指针(多级间接寻址) - 指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。 - 通常,一个指针包含一个变量的地址。当定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。
    • 指向指针的指针变量声明: -
      int **var;
      
    • 示例:
    #include 
    
    using namespace std;
    
    int main ()
    {
        int  var;
        int  *ptr;
        int  **pptr;
    
        var = 3000;
    
        // 获取 var 的地址
        ptr = &var;
    
        // 使用运算符 & 获取 ptr 的地址
        pptr = &ptr;
    
        // 使用 pptr 获取值
        cout << "var 值为 :" << var << endl;
        cout << "*ptr 值为:" << *ptr << endl;
        cout << "**pptr 值为:" << **pptr << endl;
    
        return 0;
    }
    // var 值为 :3000
    // *ptr 值为:3000
    // **pptr 值为:3000
    

    传递指针给函数

    • 方法:声明函数参数为指针类型
    • 示例:传递一个无符号的 long 型指针给函数
    #include 
    #include 
    
    using namespace std;
    void getSeconds(unsigned long *par);
    
    int main ()
    {
       unsigned long sec;
    
    
       getSeconds( &sec );
    
       // 输出实际值
       cout << "Number of seconds :" << sec << endl;
    
       return 0;
    }
    
    void getSeconds(unsigned long *par)
    {
       // 获取当前的秒数
       *par = time( NULL );
       return;
    }
    

    传递指针给函数

    • 只需要声明函数参数为指针类型,并将地址传递给函数
    • 示例:传递一个无符号的 long 型指针给函数,并在函数内改变这个值
    #include 
    #include 
    
    using namespace std;
    void getSeconds(unsigned long *par);
    
    int main ()
    {
       unsigned long sec;
    
    
       getSeconds( &sec );
    
       // 输出实际值
       cout << "Number of seconds :" << sec << endl;
    
       return 0;
    }
    
    void getSeconds(unsigned long *par)
    {
       // 获取当前的秒数
       *par = time( NULL );
       return;
    }
    
  • 同样,可以将数组传递给函数(能接受指针作为参数的函数,也能接受数组作为参数):
  • #include 
    using namespace std;
    
    // 函数声明
    double getAverage(int *arr, int size);
    
    int main ()
    {
       // 带有 5 个元素的整型数组
       int balance[5] = {1000, 2, 3, 17, 50};
       double avg;
    
       // 传递一个指向数组的指针作为参数
       avg = getAverage( balance, 5 ) ;
    
       // 输出返回值
       cout << "Average value is: " << avg << endl; 
    
       return 0;
    }
    
    double getAverage(int *arr, int size)
    {
      int    i, sum = 0;       
      double avg;          
    
      for (i = 0; i < size; ++i)
      {
        sum += arr[i];
       }
    
      avg = double(sum) / size;
    
      return avg;
    }
    // Average value is: 214.4
    

    从函数返回指针

  • 首先声明一个返回指针的函数:(C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 **static** 变量。)
    int * myFunction()
    {
    .
    .
    .
    }
    
  • 示例:
  • #include 
    #include 
    #include 
    
    using namespace std;
    
    // 要生成和返回随机数的函数
    int * getRandom( )
    {
      static int  r[10];
    
      // 设置种子
      srand( (unsigned)time( NULL ) );
      for (int i = 0; i < 10; ++i)
      {
        r[i] = rand();
        cout << r[i] << endl;
      }
    
      return r;
    }
    
    // 要调用上面定义函数的主函数
    int main ()
    {
       // 一个指向整数的指针
       int *p;
    
       p = getRandom();
       for ( int i = 0; i < 10; i++ )
       {
           cout << "*(p + " << i << ") : ";
           cout << *(p + i) << endl;
       }
    
       return 0;
    }
    

    1.19 C++ 引用

    • 引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。- 一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。

    引用 vs 指针

    区别:

    • 不存在空引用。引用必须连接到一块合法的内存。- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。- 引用必须在创建时被初始化。指针可以在任何时间被初始化。- 引用不占内存,但指针是一个变量,是有自己的独立内存空间的

    创建引用

    • 变量名称是变量附属在内存位置中的标签,引用相当于是变量附属在内存位置中的第二个标签。可以通过原始变量名称或引用来访问变量的内容。
    • 示例:
    #include 
    
    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;
    }
    // Value of i : 5
    // Value of i reference : 5
    // Value of d : 11.7
    // Value of d reference : 11.7
    
    - 引用通常用于函数参数列表和函数返回值。

    引用作为参数

    
    #include &lt;iostream&gt;
    using namespace std;
    
    // 函数声明
    void swap(int&amp; x, int&amp; y);
    
    int main ()
    {
       // 局部变量声明
       int a = 100;
       int b = 200;
    
       cout &lt;&lt; "交换前,a 的值:" &lt;&lt; a &lt;&lt; endl;
       cout &lt;&lt; "交换前,b 的值:" &lt;&lt; b &lt;&lt; endl;
    
       /* 调用函数来交换值 */
       swap(a, b);
    
       cout &lt;&lt; "交换后,a 的值:" &lt;&lt; a &lt;&lt; endl;
       cout &lt;&lt; "交换后,b 的值:" &lt;&lt; b &lt;&lt; endl;
    
       return 0;
    }
    
    // 函数定义
    void swap(int&amp; x, int&amp; y)
    {
       int temp;
       temp = x; /* 保存地址 x 的值 */
       x = y;    /* 把 y 赋值给 x */
       y = temp; /* 把 x 赋值给 y  */
    
       return;
    }
    // 交换前,a 的值: 100
    // 交换前,b 的值: 200
    // 交换后,a 的值: 200
    // 交换后,b 的值: 100
    
    

    引用作为返回值

    • 通过使用引用来替代指针,会使 C++ 程序更容易阅读和维护。函数返回一个引用的方式与返回一个指针类似。 - 当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边。
    • 示例:
    #include 
    
    using namespace std;
    
    double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
    
    double& setValues( int i )
    {
      return vals[i];   // 返回第 i 个元素的引用
    }
    
    // 要调用上面定义函数的主函数
    int main ()
    {
    
       cout << "改变前的值" << endl;
       for ( int i = 0; i < 5; i++ )
       {
           cout << "vals[" << i << "] = ";
           cout << vals[i] << endl;
       }
    
       setValues(1) = 20.23; // 改变第 2 个元素
       setValues(3) = 70.8;  // 改变第 4 个元素
    
       cout << "改变后的值" << endl;
       for ( int i = 0; i < 5; i++ )
       {
           cout << "vals[" << i << "] = ";
           cout << vals[i] << endl;
       }
       return 0;
    }
    
  • 当返回一个引用时,被引用的对象不能超出作用域。返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用。
  • int& func() {
       int q;
       // return q; // 在编译时发生错误
       static int x;
       return x;     // 安全,x 在函数作用域外依然是有效的
    }
    

    1.20 C++ 日期 & 时间

    • C++ 继承了 C 语言用于日期和时间操作的结构和函数。为了使用日期和时间相关的函数和结构,需要在 C++ 程序中引用 <ctime> 头文件。
    • 有四个与时间相关的类型:clock_t、time_t、size_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; // 夏令时
      }
    • 函数:
    序号 函数 & 描述
    1 该函数返回系统的当前日历时间,自 1970 年 1 月 1 日以来经过的秒数。如果系统没有时间,则返回 .1。 2 该返回一个表示当地时间的字符串指针,字符串形式 **day month year hours:minutes:seconds year\n\0**。 3 该函数返回一个指向表示本地时间的 **tm** 结构的指针。 4 该函数返回程序执行起(一般为程序的开头),处理器时钟所使用的时间。如果时间不可用,则返回 .1。 5 该函数返回一个指向字符串的指针,字符串包含了 time 所指向结构中存储的信息,返回形式为:day month date hours:minutes:seconds year\n\0。 6 该函数返回一个指向 time 的指针,time 为 tm 结构,用协调世界时(UTC)也被称为格林尼治标准时间(GMT)表示。 7 该函数返回日历时间,相当于 time 所指向结构中存储的时间。 8 该函数返回 time1 和 time2 之间相差的秒数。 9 该函数可用于格式化日期和时间为指定的格式。
    - 参考: ## 1.21 C++ 基本输入输出
  • C++ 的 I/O 发生在流中,流是字节序列。
    • - 如果字节流是从设备(如键盘、磁盘驱动器、网络连接等)流向内存,这叫做**输入操作**。- 如果字节流是从内存流向设备(如显示屏、打印机、磁盘驱动器、网络连接等),这叫做**输出操作**。 ### I/O 库头文件
  • 头文件函数和描述 |------ ``该文件定义了 **cin、cout、cerr** 和 **clog** 对象,分别对应于标准输入流、标准输出流、非缓冲标准错误流和缓冲标准错误流。 ``该文件通过所谓的参数化的流操纵器(比如 **setw** 和 **setprecision**),来声明对执行标准化 I/O 有用的服务。 ``该文件为用户控制的文件处理声明服务。我们将在文件和流的相关章节讨论它的细节。

    标准输出流(cout)

    • 预定义的对象 coutiostream 类的一个实例。cout 对象"连接"到标准输出设备,通常是显示屏。cout 是与流插入运算符 << 结合使用的。- cout << "Value of str is : " << 变量名 << endl;

    标准输入流(cin)

    • 预定义的对象 ciniostream 类的一个实例。cin 对象附属到标准输入设备,通常是键盘。cin 是与流提取运算符 >> 结合使用的,如下所示:- cin >> name- 流提取运算符 >> 在一个语句中可以多次使用,如果要求输入多个数据,可以使用如下语句:cin >> name >> age;

    标准错误流(cerr)

    • 预定义的对象 cerriostream 类的一个实例。cerr 对象附属到标准错误设备,通常也是显示屏,但是 cerr 对象是非缓冲的,且每个流插入到 cerr 都会立即输出。
    • cerr 也是与流插入运算符 << 结合使用的,示例:
    #include 
    
    using namespace std;
    
    int main( )
    {
       char str[] = "Unable to read....";
    
       cerr << "Error message : " << str << endl;
    }
    // Error message : Unable to read....
    

    标准日志流(clog)

    • 预定义的对象 clogiostream 类的一个实例。clog 对象附属到标准错误设备,通常也是显示屏,但是 clog 对象是缓冲的。这意味着每个流插入到 clog 都会先存储在缓冲区,直到缓冲填满或者缓冲区刷新时才会输出。
    • clog 也是与流插入运算符 << 结合使用的,如下所示:
    #include 
    
    using namespace std;
    
    int main( )
    {
       char str[] = "Unable to read....";
    
       clog << "Error message : " << str << endl;
    }
    // Error message : Unable to read....
    

    1.22 C++ 结构体

    • 结构体允许存储不同类型的数据项
    • 定义:
      struct type_name {
      member_type1 member_name1;
      member_type2 member_name2;
      member_type3 member_name3;
      .
      .
      } object_names;
    • 访问结构成员
    • - 使用**成员访问运算符(.)** 结构体可以作为函数参数

    指向结构体的指针:

    
    struct Books *struct_pointer;
    
    

    使用指向该结构的指针访问结构的成员,使用 -> 运算符

    typedef 关键字

  • 为创建的结构体起“别名”:直接使用 Books 来定义 Books 类型的变量,不需要使用 struct 关键字。
  • typedef struct Books
    {
       char  title[50];
       char  author[50];
       char  subject[100];
       int   book_id;
    }Books;
    
    // 使用:
    Books Book1, Book2;
    
  • 也可以定义非结构体类型: x, y 和 z 都是指向长整型 long int 的指针。
    typedef long int *pint32;
    
  • pint32 x, y, z;

  • typedef 与 #define 的区别:
    • typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
    • typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。

你可能感兴趣的:(c++基础算法,c++,开发语言)