C++(一)

简介

C++ 是一种静态类型的、编译式的、通用的、大小写敏感的、不规则的编程语言,支持过程化编程、面向对象编程和泛型编程
C++ 被认为是一种中级语言,它综合了高级语言和低级语言的特点
C++ 是由 Bjarne Stroustrup 于 1979 年在贝尔实验室开始设计开发的。C++ 进一步扩充和完善C,是 C 的一个超集,事实上,任何合法的 C 程序都是合法的 C++ 程序
注意:使用静态类型的编程语言是在编译时执行类型检查,而不是在运行时执行类型检查
2014 ISO/IEC 14882:2014 C++14 第四个C++标准

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

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

语法

  • 程序结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <iostream>
    using namespace std;

    // main() 是程序开始执行的地方 ,单行注释

    int main()
    {
    cout << "Hello World"; // 输出 Hello World
    return 0;
    }
    • 包含头文件<iostream>
    • using namespace std;使用std命名空间
    • int main()主函数,程序从这里开始执行
    • return 0 终止main()函数,返回0
  • 编译执行
    1
    2
    3
    4
    $ touch hello.cpp
    $ g++ hello.cpp
    $ ./a.exe
    Hello World
  • 分号&快

    1
    2
    3
    4
    5
    6
    x = y; y = y+1; add(x, y);

    {
    cout << "Hello World"; // 输出 Hello World
    return 0;
    }
  • 标识符以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母,数字,下划线,不允许出现标点字符,比如 @、& 和 %,区分大小写的编程语言

  • 关键字
  • 注释

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /* 用于输出 Hello World 的注释
    */

    cout << "Hello World"; // 输出 Hello World

    #if 0 ... #endif 来实现注释,且可以实现嵌套,格式为:
    #if 0
    code
    #endif
    你可以把 #if 0 改成 #if 1 来执行 code 的代码,测试时使用 #if 1 来执行测试代码,发布后使用 #if 0 来屏蔽测试代码
    #if 后可以是任意的条件语句
  • C++中”\n”与endl的区别

    “\n” 表示内容为一个回车符的字符串。std::endl 是流操作子,输出的作用和输出 “\n” 类似,但可能略有区别。
    std::endl 输出一个换行符,并立即刷新缓冲区。
    例如:
    std::cout << std::endl;
    相当于:
    std::cout << ‘\n’ << std::flush;
    或者
    std::cout << ‘\n’; std::fflush(stdout);
    由于流操作符 << 的重载,对于 ‘\n’ 和 “\n”,输出效果相同。
    对于有输出缓冲的流(例如cout、clog),如果不手动进行缓冲区刷新操作,将在缓冲区满后自动刷新输出。不过对于 cout 来说(相对于文件输出流等),缓冲一般体现得并不明显。但是必要情况下使用 endl 代替 ‘\n’ 一般是个好习惯。
    对于无缓冲的流(例如标准错误输出流cerr),刷新是不必要的,可以直接使用 ‘\n

数据类型

  • 1
    2
    3
    cout << "bool: \t\t" << "所占字节数:" << sizeof(bool);  
    cout << "\t最大值:" << (numeric_limits<bool>::max)();
    cout << "\t\t最小值:" << (numeric_limits<bool>::min)() << endl;

它是一种 整型 类型,里面保存的是一个整数,就像 int, long 那样。这种整数用来记录一个大小(size)。size_t 的全称应该是 size type,就是说一种用来记录大小的数据类型,通常我们用 sizeof(XXX) 操作,这个操作所得到的结果就是 size_t 类型,因为 size_t 类型的数据其实是保存了一个整数,所以它也可以做加减乘除,也可以转化为 int 并赋值给 int 类型的变量,类似的还有 wchar_t, ptrdiff_t,wchar_t 就是 wide char type,一种用来记录一个宽字符的数据类型
ptrdiff_t 就是 pointer difference type, 一种用来记录两个指针之间的距离的数据类型
通常,size_t 和 ptrdiff_t 都是用 typedef 来实现的。你可能在某个头文件里面找到类似的语句
通常我们用 sizeof(XXX) 操作,这个操作所得到的结果就是 size_t 类型
因为 size_t 类型的数据其实是保存了一个整数,所以它也可以做加减乘除,也可以转化为 int 并赋值给 int 类型的变量
类似的还有 wchar_t, ptrdiff_t
wchar_t 就是 wide char type, 一种用来记录一个宽字符的数据类型
ptrdiff_t 就是 pointer difference type, 一种用来记录两个指针之间的距离的数据类型
通常,size_t 和 ptrdiff_t 都是用 typedef 来实现的。你可能在某个头文件里面找到类似的语句:
typedef unsigned int size_t;
而 wchar_t 则稍有不同。在一些旧的编译器中,wchar_t 也可能是用 typedef 来实现,但是新的标准中 wchar_t 已经是 C/C++ 语言的关键字,wchar_t 类型的地位已经和 char, int 的地位等同了。
在标准 C/C++ 的语法中,只有 int float char bool 等基本的数据类型,至于 size_t, 或 size_type 都是以后的编程人员为了方便记忆所定义的一些便于理解的由基本数据类型的变体类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef unsigned int size_t;

int i; // 定义一个 int 类型的变量 i
size_t size=sizeof(i); // 用 sizeof 操作得到变量i的类型的大小
// 这是一个size_t类型的值
// 可以用来对一个size_t类型的变量做初始化

i=(int)size; // size_t 类型的值可以转化为 int 类型的值
char c='a'; // c 保存了字符 a,占一个字节
wchar_t wc=L'a'; // wc 保存了宽字符 a,占两个字节
// 注意 'a' 表示字符 a,L'a' 表示宽字符 a

int arr[]={1,2,3,4,5}; // 定义一个数组
int *p1=&arr[0]; // 取得数组中元素的地址,赋值给指针
int *p2=&arr[3];
ptrdiff_t diff=p2-p1; // 指针的减法可以计算两个指针之间相隔的元素个数
// 所得结果是一个 ptrdiff_t 类型

i=(int)diff; // ptrdiff_t 类型的值可以转化为 int 类型的值

  • typedef 声明,定义已有类型别名
    1
    typedef int feet;

typedef 可以声明各种类型名,但不能用来定义变量。用 typedef 可以声明数组类型、字符串类型,使用比较方便。
用typedef只是对已经存在的类型增加一个类型名,而没有创造新的类型。
当在不同源文件中用到同一类型数据(尤其是像数组、指针、结构体、共用体等类型数据)时,常用 typedef 声明一些数据类型,把它们单独放在一个头文件中,然后在需要用到它们的文件中用 #include 命令把它们包含进来,以提高编程效率。
使用 typedef 有利于程序的通用与移植。有时程序会依赖于硬件特性,用 typedef 便于移植

  • 枚举
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    enum 枚举名{ 
    标识符[=整型常数],
    标识符[=整型常数],
    ...
    标识符[=整型常数]
    } 枚举变量;

    enum color { red, green, blue } c;
    c = blue;

    enum color { red, green=5, blue };
    red值为0,green值为5,blue值为6

变量

  • 变量类型

    1
    2
    3
    4
    5
    6
    float  f, salary;
    double d;
    extern int d = 3, f = 5; // d 和 f 的声明
    int d = 3, f = 5; // 定义并初始化 d 和 f
    byte z = 22; // 定义并初始化 z
    char x = 'x'; // 变量 x 的值为 'x'
  • 变量声明

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    #include <iostream>
    using namespace std;

    // 变量声明
    extern int a, b;
    extern int c;
    extern float f;

    int main ()
    {
    // 变量定义
    int a, b;
    int c;
    float f;

    // 实际初始化
    a = 10;
    b = 20;
    c = a + b;

    cout << c << endl ;

    f = 70.0/3.0;
    cout << f << endl ;

    return 0;
    }

    // 函数声明
    int func();

    int main()
    {
    // 函数调用
    int i = func();
    }

    // 函数定义
    int func()
    {
    return 0;
    }
  • 左值lvalue 右值Rvalues
    左值(lvalue):指向内存位置的表达式被称为左值(lvalue)表达式。左值可以出现在赋值号的左边或右边。
    右值(rvalue):术语右值(rvalue)指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式,也就是说,右值可以出现在赋值号的右边,但不能出现在赋值号的左边

    1
    2
    3
    4
    有效表达式
    int g = 20;
    但是下面这个就不是一个有效的语句,会生成编译时错误:
    10 = 20;

变量的类型间是可以互相转换的,转换又分为自动转换和强制转换
自动转换规则:
1、若参与运算量的类型不同,则先转换成同一类型,然后进行运算。
2、转换按数据长度增加的方向进行,以保证精度不降低。如int型和long型运算时,先把int量转成long型后再进行运算。
a、若两种类型的字节数不同,转换成字节数高的类型
b、若两种类型的字节数相同,且一种有符号,一种无符号,则转换成无符号类型
3、所有的浮点运算都是以双精度进行的,即使仅含float单精度量运算的表达式,也要先转换成double型,再作运算。
4、char型和short型参与运算时,必须先转换成int型。
5、在赋值运算中,赋值号两边量的数据类型不同时,赋值号右边量的类型将转换为左边量的类型。如果右边量的数据类型长度比左边长时,将丢失一部分数据,这样会降低精度:

1
2
3
4
5
6
7
int a=1;
double b=2.5;
a=b;
cout << a; //输出为 2,丢失小数部分
int a = 1;
double b = 2.1;
cout << "a + b = " << a + b << endl; //输出为a + b = 3.1

强制转换规则:
强制类型转换是通过类型转换运算来实现的。其一般形式为:(类型说明符)(表达式)其功能是把表达式的运算结果强制转换成类型说明符所表示的类型

1
2
3
int a = 1;
double b = 2.1;
cout << "a + b = " << a + (int)b << endl; //输出为a + b = 3

  • 局部变量,全局变量
    局部变量和全局变量的名称可以相同,但是在函数内,局部变量的值会覆盖全局变量的值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include <iostream>
    using namespace std;

    // 全局变量声明
    int g = 20;

    int main ()
    {
    // 局部变量声明
    int g = 10;

    cout << g;

    return 0;
    }

定义全局变量时,系统会自动初始化为下列值

1
2
3
4
5
int	0
char '\0'
float 0
double 0
pointer NULL

全局变量从定义处开始至程序结束起作用,即全局变量存在有效作用域

1
2
3
4
5
6
7
8
include<iostream>
using namespace std;
int main()
{
cout<<"a= "<<a<<endl; //编译不通过,a是未知字符
return 0;
}
int a=10; //全局变量从此处定义

若要想让 main 函数也使用全局变量 a,可以用 extern 对全局变量进行声明,就可以合法使用了。

1
2
3
4
5
6
7
8
9
10
include<iostream>
using namespace std;

int main()
{
extern int a;
cout<<"a= "<<a<<endl; //合法,输出10
return 0;
}
int a=10; //全局变量从此处定义

常量

  • 整数常量

    1
    2
    3
    4
    5
    6
    7
    85         // 十进制
    0213 // 八进制
    0x4b // 十六进制
    30 // 整数
    30u // 无符号整数
    30l // 长整数
    30ul // 无符号长整数, U 和 L 的顺序任意
  • 浮点常量

    1
    2
    3
    4
    5
    3.14159        // 合法的
    314159E-5L // 合法的
    510E // 非法的:不完整的指数
    210f // 非法的:没有小数或指数
    .e55 // 非法的:缺少整数或分数
  • 布尔常量

    1
    2
    true 值代表真
    false 值代表假
  • 字符常量

  • 定义常量,不能修改赋值,定义时初始化
    使用 #define 预处理器
    使用 const 关键字

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #define LENGTH 10   
    #define WIDTH 5
    #define NEWLINE '\n'

    const int LENGTH = 10;
    const int WIDTH = 5;
    area = LENGTH * WIDTH;
    cout << area;
    cout << NEWLINE;

    宏定义 #define 和常量 const 的区别
    类型和安全检查不同
    宏定义是字符替换,没有数据类型的区别,同时这种替换没有类型安全检查,可能产生边际效应等错误;
    const常量是常量的声明,有类型区别,需要在编译阶段进行类型检查
    编译器处理不同
    宏定义是一个”编译时”概念,在预处理阶段展开,不能对宏定义进行调试,生命周期结束与编译时期;
    const常量是一个”运行时”概念,在程序运行使用,类似于一个只读行数据
    存储方式不同
    宏定义是直接替换,不会分配内存,存储与程序的代码段中;
    const常量需要进行内存分配,存储与程序的数据段中
    定义域不同

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void f1 ()
    {
    #define N 12
    const int n 12;
    }
    void f2 ()
    {
    cout<<N <<endl; //正确,N已经定义过,不受定义域限制
    cout<<n <<endl; //错误,n定义域只在f1函数中
    }

定义后能否取消
宏定义可以通过#undef来使之前的宏定义失效
const常量定义后将在定义域内永久有效

1
2
3
4
5
6
7
8
void f1()
{
#define N 12
const int n = 12;

#undef N //取消宏定义后,即使在f1函数中,N也无效了
#define N 21//取消后可以重新定义
}

宏定义不能作为参数传递给函数
const常量可以在函数的参数列表中出现

修饰符

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    int main()
    {
    short int i; // 有符号短整数
    short unsigned int j; // 无符号短整数

    j = 50000;

    i = j;
    cout << i << " " << j;

    return 0;
    }

    -15536 50000
  • const 类型的对象在程序执行期间不能被修改改变

  • volatile 修饰符 volatile 告诉编译器,变量的值可能以程序未明确指定的方式被改变
  • restrict 由 restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict
  • explicit构造函数是用来防止隐式转换的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    class Test1
    {
    public:
    Test1(int n)
    {
    num=n;
    }//普通构造函数
    private:
    int num;
    };
    class Test2
    {
    public:
    explicit Test2(int n)
    {
    num=n;
    }//explicit(显式)构造函数
    private:
    int num;
    };
    int main()
    {
    Test1 t1=12;//隐式调用其构造函数,成功
    Test2 t2=12;//编译错误,不能隐式调用其构造函数
    Test2 t2(12);//显式调用成功
    return 0;
    }

    普通构造函数能够被隐式调用。而explicit构造函数只能被显式调用

存储类

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

  • register 定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置),寄存器只用于需要快速访问的变量,比如计数器。还应注意的是,定义 ‘register’ 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制
  • static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。在 C++ 中,当 static 用在类数据成员上时,会导致仅有一个该成员的副本被类的所有对象共享

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    #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
  • std standard标准的缩写,标准库函数使用的命名空间,cin 用于从控制台获取用户输入,cout 用于将数据输出到控制台。cin 是输入流对象,cout 是输出流对象,它们分别可以用 >> 和 <<,是因为分别在其类中对相应运算符进行了重载,头文件 <iostream> 在这个命名空间内声明了 istreamostream 等 IO 类,同时在 std 内声明了 istream cin;ostream cout;

  • 静态局部变量有以下特点:
    • 该变量在全局数据区分配内存;
    • 静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
    • 静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
    • 它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束
  • extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候,如下所示:

    • 第一个文件:main.cpp

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      实例
      #include <iostream>

      int count ;
      extern void write_extern();

      int main()
      {
      count = 5;
      write_extern();
      }
    • 第二个文件:support.cpp

      1
      2
      3
      4
      5
      6
      7
      8
      9
      实例
      #include <iostream>

      extern int count;

      void write_extern(void)
      {
      std::cout << "Count is " << count << std::endl;
      }
    • 在这里,第二个文件中的 extern 关键字用于声明已经在第一个文件 main.cpp 中定义的 count。编译这两个文件

      1
      2
      3
      4
      5
      $ g++ main.cpp support.cpp -o write
      这会产生 write 可执行程序,尝试执行 write,它会产生下列结果:

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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; // 本地变量
    }

运算符

  • 算术运算符 + - * / % ++ --
  • 关系运算符 == != > < >= <=
  • 逻辑运算符 && || !
  • 位运算符
  • 赋值运算符,先运算再赋值
  • 杂项运算符

    1
    2
    3
    4
    5
    6
    7
    sizeof 返回变量的大小
    Condition ? X : Y 条件运算符,Condition 为真 ? 则值为 X : 否则值为 Y
    , 逗号运算符,var = (count=19, incr=10, count+1); 赋值以最后一个表达式运算的结果,var=20
    . 和 -> 成员运算符,引用类,结构,共用体的成员
    Cast 强制转换运算符
    & 指针运算符,返回变量的地址,&a 给出变量的实际地址
    * 指针运算符,指向一个变量,*var 指向变量var
  • 运算符优先级,由高到低

    if(i<j<k) 和 if(i<j && j<k)第一个i<j或者为0或者为1,只要k大于1,表达式就为true第二个必须i<j且j<k表达式才为true区分 if(val) 和 if(val == true)第一个只要val非零则表达式为true,val为0则表达式为false第二个只有val为1表达式为true,val非1则表达式为false

    1
    2
    3
    4
    int val = 2;
    if(val==true){ //不会进入if
    cout<<"val==true"<<endl;
    }

多个赋值操作符中,各对象必须具有相同的数据类型,或者具有可转换为同一类型的数据类型。

1
2
3
4
int ival; int *pval;
ival = pval = 0; //error 尽管ival和pval都可以赋值为0
string s1,s2;
s1 = s2 = "OK" //ok

如果指针指向不是用new分配的内存地址,则在该指针上使用delete是不合法的
通常编译器不能断定一个指针是否指向动态对象,因此尽管这样做是错误的,但在大部分编译器上仍能运行通过,但是会产生运行时错误。整形提升
对于所有比int小的整形(char, signed char, unsigned char, short, unsigned short),如过该类型所有可能值都包含在int中,他们会被提升为int型,否则,他们将被提升为unsigned int
对于包含signed和unsigned int型的表达式,表达式中的signed型整数会被转换为unsigned型

1
2
3
4
5
6
int i = -5;
unsigned int ii = 1;
cout << (i > ii) << endl; //输出1,原因是int型的i转换为unsigned int型
short s = -5;
unsigned short ss = 1;
cout << (s > ss) << endl; //输出0 比较时short和unsigned short都提升为int型

循环

  • while for do...while 嵌套循环
  • break 终止;continue 跳过主体剩余代码,重新开始下个条件;goto 跳转到被标记的语句,不建议使用
  • 无限循环,表达式留空,假设为真
    1
    2
    3
    4
    for( ; ; )
    {
    printf("This loop will run forever.\n");
    }

判断

  • if if...else 嵌套if switch 嵌套switch
  • ?: 条件运算符

函数 指针 指针数组 多级指针 指针形参或数组形参 返回值指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
#include <iostream>
#include <ctime>

using namespace std;

int max(int, int);

void fun_swap_value(int, int);

void fun_swap_point(int *, int *);

void fun_swap_cite(int, int);

void fun_pptr();

void ptrArray();

void ptrCompare();

void getSeconds(unsigned long *par);

void getAvg(int *ptr, int size);

int *getRandom();

int main() {
int a = 100;//实际整型变量
int b = 200;
char var1[10];
//变量的地址
cout << "a address " << &a << endl;
cout << "var1 address " << &var1 << endl;
//指针是一个变量,值为变量的地址,下面不同类型的指针的值
//都是代表内存地址的十六进制数,指针所指向的变量类型不同
int *ip = &a;//定义整型指针,并赋值变量的地址
//输出在指针变量中存储的地址
cout << "ip address " << ip << endl;
//访问指针中地址的变量值
cout << "*ip value " << *ip << endl;
double *dp;//double型的指针
float *fp;//float型的指针
char *cp;//char型的指针

//为指针变量赋一个 NULL 值是一个良好的编程习惯
//赋为 NULL 值的指针被称为空指针
//NULL 指针是一个定义在标准库中的值为零的常量
//该指针不指向一个可访问的内存位置
int *ptr0 = NULL;
cout << "ptr value is " << ptr0 << endl;
if (!ptr0) cout << "ptr is NULL" << endl;

int var[3] = {10, 100, 200};
int *ptr;
ptr = var;
//指针中第一个元素的地址,这里不使用&var,指针类型不一致,但是与&var[0]相同
//因为ptr指向的是int *var[0]的地址,而不是int[3] *var
//ptr = &var[0];//也可以使用这种方式
cout << "*int[3] &var address " << &var << endl;
for (int i = 0; i < 3; ++i) {
cout << "index " << i << " address is " << ptr << " value is " << *ptr << endl;
//移动到下一个位置
ptr++;
//index 0 address is 0x61feec value is 10
//index 1 address is 0x61fef0 value is 100
//index 2 address is 0x61fef4 value is 200
//如果这里指针指向的变量是整型数据 1000,占4个字节,移动到下个整数,当前位置往后移4个字节,地址是1004
//相应的如果这个变量是字符数据,占1个字节,移动到下个字符,当前位置往后移1个字节,地址是1001
}

//var++;不正确,是数组第一个元素的常量,不能作为左值
//var[2]=500;
*(var + 2) = 500;

cout << "---------" << endl;
ptrCompare();
cout << "---------" << endl;

//指针数组,包含指针的数组
ptrArray();

cout << "---------" << endl;

//二级指针,指向指针的指针,多级间接寻址
fun_pptr();

cout << "---------" << endl;
int ret = max(a, b);
cout << "max :" << ret << endl;

cout << "a=" << a << endl;
cout << "b=" << b << endl;
fun_swap_value(a, b);
cout << "fun_swap_value a=" << a << endl;
cout << "fun_swap_value b=" << b << endl;

/** 调用函数来交换值
* &a 表示指向 a 的指针,即变量 a 的地址
* &b 表示指向 b 的指针,即变量 b 的地址
*/
fun_swap_point(&a, &b);
cout << "fun_swap_point a=" << a << endl;
cout << "fun_swap_point b=" << b << endl;

fun_swap_cite(a, b);
cout << "fun_swap_cite a=" << a << endl;
cout << "fun_swap_cite b=" << b << endl;
cout << "---------" << endl;

/**传递指针给函数,声明函数参数为指针类型*/
unsigned long sec;
getSeconds(&sec);
// 输出实际值
cout << "Number of seconds :" << sec << endl;
cout << "---------" << endl;
int balance[5] = {1000, 2000, 3000, 4000, 6000};
getAvg(balance, 5);
cout << "---------" << endl;

int *random = getRandom();
for (int j = 0; j < 10; ++j) {
cout << "*(random + " << j << ") : ";
cout << *(random + j) << endl;
}

cout << "---------" << endl;

LambdaTest t;
t.lambda();
cout << "----END-----" << endl;
return 0;
}

void ptrCompare() {
int var[3] = {10, 100, 200};
int *ptr;
ptr = var;
int i = 0;
while (ptr <= &var[2]) {
cout << "index " << i << " address is " << ptr << " value is " << *ptr << endl;
//指向下一个位置
ptr++;
i++;
}
}

void ptrArray() {
int var[3] = {10, 100, 200};
int *ptrAry[3];
for (int j = 0; j < 3; ++j) {
ptrAry[j] = &var[j];
}
for (int k = 0; k < 3; ++k) {
cout << "Value of ptrAry[" << k << "] = ";
cout << *ptrAry[k] << endl;
}

const char *names[4] = {
"Zara Ali",
"Hina Ali",
"Nuha Ali",
"Sara Ali",
};
for (int m = 0; m < 4; m++) {
cout << "Value of names[" << m << "] = ";
cout << names[m] << endl;
cout << &names[m] << endl;
cout << *names[m] << endl;//指向的是字符串的第一个字符
cout << *names[m] + 1 << endl;//第一个字符+1 转换ASCII值
cout << *(names[m] + 1) << endl;//指向的是字符串的第二个字符
}
}

/**多级间接寻址*/
void fun_pptr() {
int var;
int *ptr;
int **pptr;

var = 3000;

ptr = &var;
pptr = &ptr;
cout << "Value of var :" << var << endl;
cout << "Value available at *ptr :" << *ptr << endl;
cout << "Value available at **pptr :" << **pptr << endl;
}

void getSeconds(unsigned long *par) {
*par = time(NULL);
}

void getAvg(int *ptr, int size) {
int sum = 0;
for (int i = 0; i < size; ++i) {
sum += ptr[i];
}
double avg = double(sum) / size;
cout << "avg is " << avg << endl;
}

/**函数返回指针*/
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 max(int a, int b) {
return a > b ? a : b;
}

/**
* 传值调用,默认情况下,C++ 使用传值调用来传递参数
* 实际参数的参数值传值给形式参数,调用不会影响实际参数*/
void fun_swap_value(int a, int b) {
int temp = a;
a = b;
b = temp;
}

/**指针调用
* 实际参数的地址传给形式参数,修改形式参数会影响实际参数
*/
void fun_swap_point(int *a, int *b) {
int temp;
temp = *a; // 保存地址 x 的值
*a = *b; //把 y 赋值给 x
*b = temp; //把 x 赋值给 y
}

/**引用调用
* 把引用的地址复制给形式参数。在函数内,
* 该引用用于访问调用中要用到的实际参数,修改形式参数会影响实际参数*/
void fun_swap_cite(int &a, int &b) {
int temp;
temp = a;
a = b;
b = temp;
}

Lambda表达式

C++11 提供了对匿名函数的支持,称为 Lambda 函数
Lambda 表达式本质上与函数声明非常类似。Lambda 表达式具体形式
capture mutable ->return-type{statement}
[capture]:捕捉列表。捕捉列表总是出现在 lambda 表达式的开始处。事实上,[] 是 lambda 引出符。编译器根据该引出符判断接下来的代码是否是 lambda 函数。捕捉列表能够捕捉上下文中的变量供 lambda 函数使用。
(parameters):参数列表。与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号 () 一起省略。
mutable:mutable 修饰符。默认情况下,lambda 函数总是一个 const 函数,mutable 可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空)。
->return_type:返回类型。用追踪返回类型形式声明函数的返回类型。出于方便,不需要返回值的时候也可以连同符号 -> 一起省略。此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导。
{statement}:函数体。内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量
在Lambda表达式内可以访问当前作用域的变量,这是Lambda表达式的闭包(Closure)行为。
与JavaScript闭包不同,C++变量传递有传值和传引用的区别。可以通过前面的[]来指定:
[] // 沒有定义任何变量。使用未定义变量会引发错误。
[x, &y] // x以传值方式传入(默认),y以引用方式传入。
[&] // 任何被使用到的外部变量都隐式地以引用方式加以引用。
[=] // 任何被使用到的外部变量都隐式地以传值方式加以引用。
[&, x] // x显式地以传值方式加以引用。其余变量以引用方式加以引用。
[=, &z] // z显式地以引用方式加以引用。其余变量以传值方式加以引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class LambdaTest {
public:
void lambda() {

int global_x = 0;
//没有返回值
[&global_x] { ++global_x; };
//返回true,false
[](int x, int y) { return x > y; };
//指定返回int
[](int x, int y) -> int {
int z = x + y;
return z + x;
};
[this]() { this->someFunc(); }();
[] {};//最简单的lambda表达式
}

/**函数调用运算符的重载方法是const属性的
* 有时候,你想改动传值方式捕获的值,那么就要使用mutable*/
void someFunc() {
int x = 10;

auto add_x = [x](int a) mutable {
x *= 2;
return a + x;
}; // 复制捕捉x

cout << add_x(10) << endl; // 输出 30
}
};

数字

  • 数学运算

    double cos(double);
    该函数返回弧度角(double 型)的余弦。
    double sin(double);
    该函数返回弧度角(double 型)的正弦。
    double tan(double);
    该函数返回弧度角(double 型)的正切。
    double log(double);
    该函数返回参数的自然对数。
    double pow(double, double);
    假设第一个参数为 x,第二个参数为 y,则该函数返回 x 的 y 次方。
    double hypot(double, double);
    该函数返回两个参数的平方总和的平方根,也就是说,参数为一个直角三角形的两个直角边,函数会返回斜边的长度。
    double sqrt(double);
    该函数返回参数的平方根。
    int abs(int);
    该函数返回整数的绝对值。
    double fabs(double);
    该函数返回任意一个十进制数的绝对值。
    double floor(double);
    该函数返回一个小于或等于传入参数的最大整数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    #include <iostream>
    #include <cmath>
    #include <ctime>

    using namespace std;

    int main() {
    short s = 10;
    int i = -10000;
    long l = 100000;
    float f = 230.45;
    double d = 200.43;

    cout << sin(d) << endl;
    cout << abs(i) << endl;
    cout << log(s) << endl;
    cout << pow(s, 2) << endl;
    cout << hypot(3, 4) << endl;
    cout << floor(d) << endl;
    cout << sqrt(f) << endl;

    //生成随机数,设置种子
    srand((unsigned) time(NULL));
    //生成10个随机数
    int r;
    for (int j = 0; j < 10; j++) {
    r = rand();
    cout << "random " << r << endl;
    }
    return 0;
    }

数组

  • 固定大小的相同类型元素的顺序集合,数组都是由连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    type arrayName [ arraySize ];
    double balance[10];

    double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};

    // 输出数组中每个元素的值
    for ( int j = 0; j < 10; j++ )
    {
    cout << setw( 7 )<< j << setw( 13 ) << n[ j ] << endl;
    }

    setw()函数设置显示宽度
  • 多维数组(二维数组)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    type arrayName [ x ][ y ];

    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};

    // 一个带有 5 行 2 列的数组
    int a[5][2] = { {0,0}, {1,2}, {2,4}, {3,6},{4,8}};

    // 输出数组中每个元素的值
    for ( int i = 0; i < 5; i++ )
    for ( int j = 0; j < 2; j++ )
    {
    cout << "a[" << i << "][" << j << "]: ";
    cout << a[i][j]<< endl;
    }
  • 指向数组的指针

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 带有 5 个元素的双精度浮点型数组
    double balance[5] = {1000.0, 2.0, 3.4, 17.0, 50.0};
    double *p;

    p = balance;

    // 输出数组中每个元素的值
    cout << "使用指针的数组值 " << endl;
    for ( int i = 0; i < 5; i++ )
    {
    cout << "*(p + " << i << ") : ";
    cout << *(p + i) << endl;
    }

    cout << "使用 balance 作为地址的数组值 " << endl;
    for ( int i = 0; i < 5; i++ )
    {
    cout << "*(balance + " << i << ") : ";
    cout << *(balance + i) << endl;
    }
  • 传递数组给函数

    1
    2
    3
    4
    5
    void myFunction(int *param);

    void myFunction(int param[10]);

    void myFunction(int param[]);
  • 函数返回数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // 要生成和返回随机数的函数
    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;
    }

字符串

  • 以null字符\0终止的一维字符数组组成了字符串,字符数组的大小比字符”Hello”多一个

    1
    2
    3
    char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

    char greeting[] = "Hello";
  • cstring 函数, 运用char[]

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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;
  • c++ 中的String

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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;

引用

  • 不存在空引用。引用必须连接到一块合法的内存
  • 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象
  • 引用必须在创建时被初始化。指针可以在任何时间被初始化
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    // 函数声明 引用调用函数
    void swap(int &x, int &y);

    double &setValues(int i);

    double vals[] = {10.2, 13.4, 15.6, 23.5, 67.2};

    int main() {
    int i = 17;
    double d = 15.5;
    int &r = i;
    double &s = d;

    cout << "i:" << i << endl;
    cout << "r:" << r << endl;
    cout << "d:" << d << endl;
    cout << "s:" << s << endl;

    // 局部变量声明
    int a = 100;
    int b = 200;

    cout << "a:" << a << endl;
    cout << "b:" << b << endl;

    /* 调用函数来交换值 */
    swap(a, b);

    cout << " a:" << a << endl;
    cout << " b:" << b << endl;

    setValues(1) = 18.6;
    setValues(3) = 19.3;

    for (int j = 0; j < 5; ++j) {
    cout << "j:" << j << "-" << vals[j] << endl;
    }

    //r 的值发生改变,但是指向的地址未变
    r=a;
    cout << "r: " << r << endl;
    cout<<"address a "<<&a<<endl;
    cout<<"address r "<<&r<<endl;
    cout<<"address i "<<&i<<endl;
    return 0;
    }

    // 函数定义
    void swap(int &x, int &y) {
    int temp;
    temp = x; /* 保存地址 x 的值 */
    x = y; /* 把 y 赋值给 x */
    y = temp; /* 把 x 赋值给 y */

    }

    double &setValues(int i) {
    return vals[i];// 返回第 i 个元素的引用
    }

    //(1)以引用返回函数值,定义函数时需要在函数名前加 &
    //(2)用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本。
    //引用作为返回值,必须遵守以下规则:
    // (1)不能返回局部变量的引用。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。
    // (2)不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak
    // (3)可以返回类成员的引用,但最好是const。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常 量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性
    //当返回一个引用时,要注意被引用的对象不能超出作用域
    //所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用
    int& func() {
    int q;
    //return q; // 在编译时发生错误
    static int x;
    return x; // 安全,x 在函数作用域外依然是有效的
    }

日期&时间

  • 有四个与时间相关的类型:clock_t、time_t、size_t 和 tm。类型 clock_t、size_t 和 time_t 能够把系统时间和日期表示为某种整数。

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

1
2
3
4
5
6
7
8
9
10
11
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++ 中关于日期和时间的重要函数
    1 time_t time(time_t time);
    该函数返回系统的当前日历时间,自 1970 年 1 月 1 日以来经过的秒数。如果系统没有时间,则返回 .1。
    2 char
    ctime(const time_t time);
    该返回一个表示当地时间的字符串指针,字符串形式 day month year hours:minutes:seconds year\n\0
    3 struct tm
    localtime(const time_t time);
    该函数返回一个指向表示本地时间的 tm 结构的指针
    4 clock_t clock(void);
    该函数返回程序执行起(一般为程序的开头),处理器时钟所使用的时间。如果时间不可用,则返回 .1
    5 char
    asctime ( const struct tm time );
    该函数返回一个指向字符串的指针,字符串包含了 time 所指向结构中存储的信息,返回形式为:day month date hours:minutes:seconds year\n\0
    6 struct tm
    gmtime(const time_t time);
    该函数返回一个指向 time 的指针,time 为 tm 结构,用协调世界时(UTC)也被称为格林尼治标准时间(GMT)表示
    7 time_t mktime(struct tm
    time);
    该函数返回日历时间,相当于 time 所指向结构中存储的时间
    8 double difftime ( time_t time2, time_t time1 );
    该函数返回 time1 和 time2 之间相差的秒数
    9 size_t strftime();
    该函数可用于格式化日期和时间为指定的格式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    #include <ctime>

    using namespace std;

    int main() {
    // 基于当前系统的当前日期/时间
    time_t now = time(nullptr);
    cout << "now " << now << endl;
    // 把 now 转换为字符串形式
    char *dt = ctime(&now);
    cout << "local date/time " << dt << endl;
    // 把 now 转换为 tm 结构
    tm *gmtm = gmtime(&now);
    dt = asctime(gmtm);
    cout << "UTC date/time " << dt << endl;

    tm *ltm = localtime(&now);
    cout << "Y: " << ltm->tm_year << endl;
    cout << "M: " << ltm->tm_mon << endl;
    cout << "D: " << ltm->tm_mday << endl;
    cout << "Time: " << ltm->tm_hour << ":";
    cout << " " << ltm->tm_min << ":";
    cout << " " << ltm->tm_sec << endl;

    return 0;
    }

输入输出

  • <iostream> 该文件定义了 cin、cout、cerr 和 clog 对象,分别对应于标准输入流、标准输出流、非缓冲标准错误流和缓冲标准错误流
  • <iomanip> 该文件通过所谓的参数化的流操纵器(比如 setw 和 setprecision),来声明对执行标准化 I/O 有用的服务
  • <fstream> 该文件为用户控制的文件处理声明服务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    char name[50];
    int age;
    cout << "your name age :";
    cin >> name >> age;
    cout << " name is " << name << ", age is " << age << endl;

    char str[] = "exec";

    cerr << "error " << str << endl;
    wcerr << "error " << str << endl;

    clog << "error " << str << endl;
    wclog << "error " << str << endl;

数据结构

参考

菜鸟教程

willkernel wechat
关注微信公众号
帅哥美女们,请赐予我力量吧!