数据类型

七种根本的 C++ 数据类型

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

一些根本类型能够运用一个或多个类型润饰符进行润饰:

  • signed:标明变量能够存储负数。关于整型变量来说,signed 能够省掉,由于整型变量默许为有符号类型
  • unsigned:标明变量不能存储负数。关于整型变量来说,unsigned 能够将变量规模扩展一倍
  • short:标明变量的规模比 int 更小。short int 能够缩写为 short
  • long:标明变量的规模比 int 更大。long int 能够缩写为 long
  • long long:标明变量的规模比 long 更大。C++11 中新增的数据类型润饰符

各数据类型的内存和规模

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

类型 规模
char 1 个字节 -128 到 127 或许 0 到 255
unsigned char 1 个字节 0 到 255
signed char 1 个字节 -128 到 127
int 4 个字节 -2147483648 到 2147483647
unsigned int 4 个字节 0 到 4294967295
signed int 4 个字节 -2147483648 到 2147483647
short int 2 个字节 -32768 到 32767
unsigned short int 2 个字节 0 到 65,535
signed short int 2 个字节 -32768 到 32767
long int 8 个字节 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
signed long int 8 个字节 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
unsigned long int 8 个字节 0 到 18,446,744,073,709,551,615
float 4 个字节 精度型占4个字节(32位)内存空间,+/- 3.4e +/- 38 (~7 个数字)
double 8 个字节 双精度型占8 个字节(64位)内存空间,+/- 1.7e +/- 308 (~15 个数字)
long double 16 个字节 长双精度型 16 个字节(128位)内存空间,可供给18-19位有用数字
wchar_t 2 或 4 个字节 1 个宽字符

枚举类型

例如,下面的代码界说了一个颜色枚举,变量 c 的类型为 color。最终,c 被赋值为 “blue”

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

类型判别

  1. typeid运算符:能够用于获取一个方针的类型信息,回来一个type_info方针(类型的首字母)。例如:

    int i = 42;
    float j = 42.5f;
    std::cout << typeid(i).name() << std::endl;  // 输出:i
    std::cout << typeid(j).name() << std::endl;  // 输出:f
    
  2. std::is_same类型特征:能够用于检查两种类型是否相同。例如:

    std::cout << std::is_same<int, float>::value << std::endl;  // 输出:0
    std::cout << std::is_same<int, int>::value << std::endl;    // 输出:1
    
  3. std::is_integral类型特征:能够用于检查一个类型是否为整型。例如:

    std::cout << std::is_integral<int>::value << std::endl;    // 输出:1
    std::cout << std::is_integral<float>::value << std::endl;  // 输出:0
    
  4. std::is_floating_point类型特征:能够用于检查一个类型是否为浮点型。例如:

    std::cout << std::is_floating_point<int>::value << std::endl;    // 输出:0
    std::cout << std::is_floating_point<float>::value << std::endl;  // 输出:1
    
  5. std::is_pointer类型特征:能够用于检查一个类型是否为指针类型。例如:

    std::cout << std::is_pointer<int*>::value << std::endl;    // 输出:1
    std::cout << std::is_pointer<float>::value << std::endl;  // 输出:0
    
  6. std::is_array类型特征:能够用于检查一个类型是否为数组类型。例如:

    std::cout << std::is_array<int[]>::value << std::endl;    // 输出:1
    std::cout << std::is_array<float>::value << std::endl;    // 输出:0
    
  7. std::is_function类型特征:能够用于检查一个类型是否为函数类型。例如:

    std::cout << std::is_function<int(int)>::value << std::endl;  // 输出:1
    std::cout << std::is_function<float>::value << std::endl;     // 输出:0
    

这些类型判别办法能够帮助咱们在编写泛型代码时更加灵敏和安全

类型转化

隐式类型转化

简略粗暴,可是存在问题。例如将一个float类型的值42.5转化为int类型,由于int类型不支撑小数部分,因而在进行转化时,小数部分会被切断,只保留整数部分

float f = 42.5f;
int i = int(f);
int i = (int)f;
int i = f;

显式类型转化

C 风格的类型转化:运用括号将需求转化的类型括起来,并在前面增加需求转化的类型。例如:

int i = 42;
float f = (float)i;  // C 风格的类型转化

C++ 风格的类型转化:运用static_castdynamic_castconst_castreinterpret_cast进行类型转化。例如:

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

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

动态转化dynamic_cast一般用于将一个基类指针或引证转化为派生类指针或引证

class Base {};
class Derived : public Base {};
Base* ptr_base = new Derived;
Derived* ptr_derived = dynamic_cast<Derived*>(ptr_base); // 将基类指针转化为派生类指针

常量转化const_cast用于将 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类型

数字和字符串互转

  • 字符串转 int、float
#include <iostream>
#include <string>
int main() {
    std::string str = "42";
    int i = std::stoi(str);
    std::cout << "The integer is: " << i << std::endl;
    float f = std::stof(str);
    std::cout << "The float is: " << f << std::endl;
}

这些函数都需求包括头文件<string>,承受一个字符串作为参数。假如字符串不包括有用的数字,则会抛出std::invalid_argumentstd::out_of_range反常。

需求留意的是,这些函数只能将符合特定格局的字符串转化为数字类型。例如,关于std::stoi,字符串有必要以数字最初,能够包括正负号,但不能包括其他字符。关于std::stof,字符串有必要包括小数点和数字,能够包括正负号和指数符号,但不能包括其他字符

  • int、float 转字符串
#include <iostream>
#include <string>
int main() {
    int i = 42;
    float f = 3.14;
    std::string str1 = std::to_string(i);
    std::string str2 = std::to_string(f);
    std::cout << "The integer string is: " << str1 << std::endl;
    std::cout << "The float string is: " << str2 << std::endl;
}

常量

在 C++ 中,有两种简略的界说常量的办法:

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

define 预处理器

下面是运用 #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;
}

const 关键字

您能够运用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;
}

类型限定符

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

限定符 含义
const const界说常量,标明该变量的值不能被修正。。
volatile 润饰符volatile告诉该变量的值或许会被程序以外的要素改动,如硬件或其他线程
restrict restrict润饰的指针是仅有一种拜访它所指向的方针的办法。只有 C99 增加了新的类型限定符 restrict
mutable 标明类中的成员变量能够在 const 成员函数中被修正
static 用于界说静态变量,标明该变量的效果域仅限于当时文件或当时函数内,不会被其他文件或函数拜访
register 用于界说寄存器变量,标明该变量被频繁运用,能够存储在CPU的寄存器中,以进步程序的运转功率。

const 实例

const int NUM = 10; // 界说常量 NUM,其值不行修正
const int* ptr = &NUM; // 界说指向常量的指针,指针所指的值不行修正
int const* ptr2 = &NUM; // 和上面一行等价

volatile 实例

volatile int num = 20; // 界说变量 num,其值或许会在未知的时刻被改动

mutable 实例

class Example {
public:
    int get_value() const {
        return value_; // const 关键字标明该成员函数不会修正方针中的数据成员
    }
    void set_value(int value) const {
        value_ = value; // mutable 关键字答应在 const 成员函数中修正成员变量
    }
private:
    mutable int value_;
};

static 实例

void example_function() {
    static int count = 0; // static 关键字使变量 count 存储在程序生命周期内都存在
    count++;
}

register 实例

void example_function(register int num) {
    // register 关键字主张编译器将变量 num 存储在寄存器中
    // 以进步程序履行速度
    // 可是实践上是否会存储在寄存器中由编译器决议
}

变量效果域

有三个地方能够界说变量:

  • 在函数或一个代码块内部声明的变量,称为部分变量
  • 在函数参数的界说中声明的变量,称为办法参数
  • 在一切函数外部声明的变量,称为大局变量

部分变量和大局变量

比如:

#include <iostream>
using namespace std;
int g; // 大局变量声明
int main ()
{
  int a, b; // 部分变量声明
  // 实践初始化
  a = 10;
  b = 20;
  g = a + b;
  cout << g;
}

部分变量的值会掩盖大局变量的值,比如:

#include <iostream>
using namespace std;
int g; // 大局变量声明
int main ()
{
  int g = 10; // 部分变量声明
  cout << g;
}

变量的效果域能够分为以下几种:

  • 部分效果域:在函数内部声明的变量具有部分效果域,它们只能在函数内部拜访。部分变量在函数每次被调用时被创立,在函数履行完后被毁掉
  • 大局效果域:在一切函数和代码块之外声明的变量具有大局效果域,它们能够被程序中的任何函数拜访。大局变量在程序开端时被创立,在程序完毕时被毁掉
  • 块效果域:在代码块内部声明的变量具有块效果域,它们只能在代码块内部拜访。块效果域变量在代码块每次被履行时被创立,在代码块履行完后被毁掉
  • 类效果域:在类内部声明的变量具有类效果域,它们能够被类的一切成员函数拜访。类效果域变量的生命周期与类的生命周期相同

块效果域

#include <iostream>
int main() {
  int a = 10;
  {
    int a = 20; // 块效果域变量
    std::cout << "块变量: " << a << std::endl;
  }
  std::cout << "外部变量: " << a << std::endl;
}
// 输出成果
块变量: 20
外部变量: 10

类效果域

能够运用类名和效果域解析运算符::来拜访这个变量

#include <iostream>
class MyClass {
public:
  static int class_var; // 类效果域变量
};
int MyClass::class_var = 30; // 运用类名和效果域解析运算符::来拜访这个变量
int main() {
  std::cout << "类变量: " << MyClass::class_var << std::endl;
}

C++ 内存分区

  1. 大局区Global

大局区是寄存大局变量静态变量的内存区域,在程序启动时主动分配,在程序完毕时主动开释。大局区的内存空间是接连的,由编译器主动办理。大局区的巨细也是固定的,因而只能寄存较小的数据

  1. 常量区Const

常量区是寄存常量数据的内存区域,如字符串常量、大局常量等。常量区内存只读,不行修正。常量区的内存空间是接连的,由编译器主动办理

  1. 栈区Stack

栈区是由编译器主动分配和开释的内存区域,寄存函数的参数值、部分变量等。栈区内存的分配和开释速度很快,由于它的内存空间是接连的,且由编译器主动办理。栈区的巨细是固定的,一般只能寄存较小的数据。当函数履行完毕后,栈区内存会主动开释,因而不需求手动开释栈区内存

  1. 堆区Heap

堆区是由程序员手动分配和开释的内存区域,寄存程序运转期间动态分配的内存。堆区的内存空间是不接连的,因而内存分配和开释的速度较慢,可是堆区的内存空间较大,能够寄存较大的数据。堆区内存的分配和开释需求运用newdeletemallocfree等函数手动办理

  1. 代码区Code

代码区是寄存程序的可履行代码的内存区域,由操作体系负责办理。代码区的内存空间是只读的,不行修正

运算符

算术运算符

下表显现了 C++ 支撑的算术运算符。

假定变量 A 的值为 10,变量 B 的值为 21,则:

运算符 描绘 实例
+ 把两个操作数相加 A + B 将得到 31
从第一个操作数中减去第二个操作数 A – B 将得到 -11
* 把两个操作数相乘 A * B 将得到 210
/ 取整 B / A 将得到 2
% 取余 B % A 将得到 1
++ 自增运算符,整数值增加 1 A++ 将得到 11
自减运算符,整数值减少 1 A– 将得到 9

关系运算符

下表显现了 C++ 支撑的关系运算符。

假定变量 A 的值为 10,变量 B 的值为 20,则:

运算符 描绘 实例
== 检查两个操作数的值是否相等,假如相等则条件为真。 (A == B) 不为真。
!= 检查两个操作数的值是否相等,假如不相等则条件为真。 (A != B) 为真。
检查左操作数的值是否大于右操作数的值,假如是则条件为真。 (A > B) 不为真。
< 检查左操作数的值是否小于右操作数的值,假如是则条件为真。 (A < B) 为真。
>= 检查左操作数的值是否大于或等于右操作数的值,假如是则条件为真。 (A >= B) 不为真。
<= 检查左操作数的值是否小于或等于右操作数的值,假如是则条件为真。 (A <= B) 为真。

逻辑运算符

下表显现了 C++ 支撑的关系逻辑运算符。

假定变量 A 的值为 1,变量 B 的值为 0,则:

运算符 描绘 实例
&& 称为逻辑与运算符。假如两个操作数都 true,则条件为 true。 (A && B) 为 false。
|| 称为逻辑或运算符。假如两个操作数中有任意一个 true,则条件为 true。 (A || B) 为 true。
! 称为逻辑非运算符。用来逆转操作数的逻辑状态,假如条件为 true 则逻辑非运算符将使其为 false。 !(A && B) 为 true。

其他一些重要的运算符

运算符 描绘
sizeof sizeof 运算符回来变量的巨细。例如,sizeof(a) 将回来 4,其间 a 是整数。
Condition ? X : Y 条件运算符。假如 Condition 为真 ? 则值为 X : 不然值为 Y。
,(逗号运算符) 逗号运算符会顺序履行一系列运算。整个逗号表达式的值是以逗号分隔的列表中的最终一个表达式的值。
.(点运算符)和 ->(箭头运算符) 成员运算符用于引证类、结构和共用体的成员
::(双冒号运算符) 用于直接拜访命名空间、类、结构体、共用体或枚举类型的成员或静态成员
Cast(强制转化运算符) 强制类型转化,把一种数据类型转化为另一种数据类型。例如,int(2.2000) 将回来 2
&(指针运算符) 指针运算符 &由变量获取到它的地址。例如 &a 得到变量 a 的地址
*(指针运算符) 指针运算符 *由地址获取到变量的值。例如 *ptr 得到地址 ptr 指向的变量

逗号运算符(,)

  • 运用逗号运算符是为了把几个表达式放在一同
  • 整个逗号表达式的值为系列中最终一个表达式的值

比如:

#include <iostream>
using namespace std;
int main()
{
   int i, j;
   j = 10;
   i = (j++, j+100, 999+j);
   cout << i; // 1010
}

成员运算符(. ->)

成员运算符用于引证类、结构和共用体的(public)成员

class Point {
public:
    int x;
    int y;
    void print() {
        std::cout << "(" << x << ", " << y << ")" << std::endl;
    }
};
int main() {
    /** 点运算符 */
    Point p;
    p.x = 5;  // 拜访成员变量
    p.print();  // 拜访成员函数
    /* 箭头运算符 */
    Point* pPtr = new Point();
    pPtr->x = 5;  // 拜访成员变量
    pPtr->print();  // 拜访成员函数
    delete pPtr;
}

双冒号运算符(::)

双冒号运算符用于拜访命名空间、枚举类型、类的成员

以下比如中演示拜访命名空间 NS 中的成员变量 x,其实 std 也是一个命名空间,其包括了 C++ 规范库中的一切标识符,例如规范输入输出流、容器、算法等等

/* 命名空间 */
namespace NS {
    int x = 42;
}
/* 类 */
class MyClass {
public:
    static int y;
};
/* 枚举类型 */
enum Color { RED, GREEN, BLUE };
int MyClass::y = 123; // 拜访类的静态成员
int main() {
    std::cout << NS::x << std::endl;  // 拜访命名空间成员
    std::cout << MyClass::y << std::endl;  // 拜访类的静态成员
    std::cout << Color::RED << std::endl;  // 拜访枚举类型成员
}

基类也是类的一种,所以拜访基类的成员也用双冒号运算符

class Base {
public:
    int x;
    void print() {
        std::cout << "Base::print()" << x << std::endl;
    }
};
class Derived : public Base {
public:
    void print() {
        Base::print();  // 调用基类的办法
        std::cout << "Derived::print()" << Base::x << std::endl;  // 拜访基类的成员
    }
};
int main() {
    Derived d;
    d.x = 42;
    d.print();
}

指针运算符(& *)

#include <iostream>
using namespace std;

int main ()
{
 int var;
 int *ptr; // * 运算符也能够用来标明一个指针
 int val;
 var = 3000;
 ptr = &var;// 获取变量 var 的地址,赋值给 ptr
 val = *ptr;// 获取地址 ptr 指向的变量 var 的值
 cout << "Value of var :" << var << endl;
 cout << "Value of ptr :" << ptr << endl;
 cout << "Value of val :" << val << endl;
 return 0;
}

注释

C++ 存在三种注释:

  • // 一般用于单行注释
  • /* ... */ 一般用于多行注释
  • #if 0 ... #endif 条件编译注释
#include <iostream>
using namespace std;
int main() {
    // 这是单行注释
    /* 这是注释 */
    /* 
     * 能够多行注释
     */ 
    cout << "Hello World!";
    return 0;
}

块注释用于程序调试,测验时运用 #if 1来履行测验代码,发布后运用 #if 0来屏蔽测验代码

#if condition
  code1
#else
  code2
#endif

根本的输入和输出

I/O 库头文件

下列的头文件在 C++ 编程中很重要。

头文件 函数和描绘
<iostream> 该文件界说了cin、cout、cerrclog方针,别离对应于规范输入流、规范输出流、非缓冲规范过错流和缓冲规范过错流。
<iomanip> 该文件通过所谓的参数化的流操纵器(比如setwsetprecision),来声明对履行规范化 I/O 有用的服务。
<fstream> 该文件为用户操控的文件处理声明服务。咱们将在文件和流的相关章节讨论它的细节。

规范输出流(cout)

#include <iostream>
using namespace std;
int main( )
{
   char str[] = "Hello C++";
   cout << "Value of str is : " << str << endl;
}

C++ 编译器依据要输出变量的数据类型,选择合适的流刺进运算符来显现值。<< 运算符被重载来输出内置类型(整型、浮点型、double 型、字符串和指针)的数据项。

流刺进运算符 << 在一个句子中能够屡次运用,如上面实例中所示,endl用于内行末增加一个换行符

规范输入流(cin)

#include <iostream>
using namespace std;
int main( )
{
   char name[50];
   cout << "请输入您的称号: ";
   cin >> name;
   cout << "您的称号是: " << name << endl;
}

流提取运算符 >> 在一个句子中能够屡次运用,假如要求输入多个数据,能够运用如下句子:

cin >> name >> age;

这相当于下面两个句子:

cin >> name;
cin >> age;

规范过错流(cerr)

预界说的方针cerriostream类的一个实例。cerr 方针隶属到规范输出设备,一般也是显现屏,可是cerr方针是非缓冲的,且每个流刺进到 cerr 都会当即输出

#include <iostream>
using namespace std;
int main( )
{
   char str[] = "Unable to read....";
   cerr << "Error message : " << str << endl;
}

规范日志流(clog)

预界说的方针clogiostream类的一个实例。clog 方针隶属到规范输出设备,一般也是显现屏,可是clog方针是缓冲的。这意味着每个流刺进到 clog 都会先存储在缓冲区,直到缓冲填满或许缓冲区改写时才会输出

#include <iostream>
using namespace std;
int main( )
{
   char str[] = "Unable to read....";
   clog << "Error message : " << str << endl;
}

日期和时刻

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; // 夏令时
};

下面是 C/C++ 中关于日期和时刻的重要函数。一切这些函数都是 C/C++ 规范库的组成部分,您能够在 C++ 规范库中检查一下各个函数的细节。

函数 描绘
time_t time(time_t *time); 该函数回来体系的当时日历时刻,自 1970 年 1 月 1 日以来通过的秒数。假如体系没有时刻,则回来 -1
char *ctime(const time_t *time); 该回来一个标明当地时刻的字符串指针,字符串办法day month year hours:minutes:seconds year\n\0
struct tm *localtime(const time_t *time); 该函数回来一个指向标明本地时刻的tm结构的指针
clock_t clock(void); 该函数回来程序履行起(一般为程序的最初),处理器时钟所运用的时刻。假如时刻不行用,则回来 -1
char * asctime ( const struct tm * time ); 该函数回来一个指向字符串的指针,字符串包括了 time 所指向结构中存储的信息,回来办法为:day month date hours:minutes:seconds year\n\0。
struct tm *gmtime(const time_t *time); 该函数回来一个指向 time 的指针,time 为 tm 结构,用协调世界时(UTC)也被称为格林尼治规范时刻(GMT)标明
time_t mktime(struct tm *time); 该函数回来日历时刻,相当于 time 所指向结构中存储的时刻
double difftime ( time_t time2, time_t time1 ); 该函数回来 time1 和 time2 之间相差的秒数
size_t strftime(); 该函数可用于格局化日期和时刻为指定的格局

当时日期和时刻

下面的实例获取当时体系的日期和时刻,包括本地时刻和协调世界时(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;
}
输出成果:
本地日期和时刻:Fri Sep 15 06:44:51 2023
UTC 日期和时刻:Fri Sep 15 06:44:51 2023

运用结构 tm 格局化时刻

C 库函数size_t strftime(char *str, size_t maxsize, const char *format, const struct tm *timeptr 依据format中界说的格局化规则,格局化结构timeptr标明的时刻,并把它存储在str中。

下面是 strftime() 函数的声明

size_t strftime(char *str, size_t maxsize, const char *format, const struct tm *timeptr)
  • str— 这是指向方针数组的指针,用来仿制产生的 C 字符串。
  • maxsize— 这是被仿制到 str 的最大字符数。
  • format— 这是 C 字符串,包括了普通字符和特殊格局说明符的任何组合。这些格局说明符由函数替换为标明 tm 中所指定时刻的相对应值。格局说明符是:
#include <stdio.h>
#include <time.h>
int main ()
{
   time_t rawtime;
   struct tm *info;
   char buffer[80];
   time( &rawtime );
   info = localtime( &rawtime );
   strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", info);
   printf("格局化的日期 & 时刻 : %s\n", buffer );
}
输出成果:
格局化的日期 & 时刻 : 2023-09-15 06:45:54

判别

判别句子

C++ 编程言语供给了以下类型的判别句子。点击链接检查每个句子的细节。

句子 描绘
if 句子 一个if 句子由一个布尔表达式后跟一个或多个句子组成
if…else 句子 一个if 句子后可跟一个可选的else 句子,else 句子在布尔表达式为假时履行
嵌套 if 句子 您能够在一个ifelse if句子内运用另一个ifelse if句子
switch 句子 一个switch句子答应测验一个变量等于多个值时的情况
嵌套 switch 句子 您能够在一个switch句子内运用另一个switch 句子

? : 运算符

咱们现已在前面的章节中讲解了条件运算符 ? : ,能够用来代替if…else句子。它的一般办法如下:

Exp1 ? Exp2 : Exp3;

其间,Exp1、Exp2 和 Exp3 是表达式。请留意,冒号的运用和方位

? 表达式的值是由 Exp1 决议的。假如 Exp1 为真,则核算 Exp2 的值,成果即为整个 ? 表达式的值。假如 Exp1 为假,则核算 Exp3 的值,成果即为整个 ? 表达式的值

循环

循环类型

C++ 编程言语供给了以下几种循环类型。点击链接检查每个类型的细节。

循环类型 描绘
while 循环 当给定条件为真时,重复句子或句子组。它会在履行循环主体之前测验条件
for 循环 屡次履行一个句子序列,简化办理循环变量的代码
do…while 循环 除了它是在循环主体完毕测验条件外,其他与 while 句子相似
嵌套循环 您能够在 while、for 或 do..while 循环内运用一个或多个循环

循环操控句子

循环操控句子更改履行的正常序列。当履行脱离一个规模时,一切在该规模中创立的主动方针都会被毁掉。

C++ 供给了下列的操控句子。点击链接检查每个句子的细节。

操控句子 描绘
break 句子 停止loopswitch句子,程序流将继续履行紧接着 loop 或 switch 的下一条句子
continue 句子 引起循环跳过主体的剩下部分,当即从头开端测验条件
goto 句子 将操控转移到被符号的句子。可是不主张在程序中运用 goto 句子

字符串

C++ 规范库供给了string类类型,需求引进 #include <string>

1. 构造字符串

string s1();  // si = ""
string s2("Hello");  // s2 = "Hello"
string s3(4, 'K');  // s3 = "KKKK"
string s4("12345", 1, 3);  //s4 = "234",即 "12345" 的从下标 1 开端,长度为 3 的子串

留意:string 类不接纳一个整型参数或一个字符型参数的构造函数。下面的两种写法是过错的

string s1('K'); // 不接纳一个字符型参数
string s2(123); // 不接纳一个整型参数

2. 求字符串长度

string s1 = "hello world";
cout << s1.length() << endl; // 11
cout << s1.size() << endl; // 11

3. 字符串拼接

除了能够运用++=运算符对 string 方针履行字符串的衔接操作外,string 类还有 append 成员函数,能够用来向字符串后面增加内容。append 成员函数回来方针本身的引证,会改动原字符串。例如:

string s1("123"), s2("abc");
s1.append(s2);  // s1 = "123abc"
s1.append(s2, 1, 2);  // s1 = "123abcbc"
s1.append(3, 'K');  // s1 = "123abcbcKKK"
s1.append("ABCDE", 2, 3);  // s1 = "123abcbcKKKCDE",增加 "ABCDE" 的子串(2, 3)

补充见 c.biancheng.net/view/400.ht…

指针

什么是内存地址

每一个变量都有一个内存方位,每一个内存方位都界说了可运用连字号(&)运算符拜访的地址,它标明这个变量在内存中的地址

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

运转成果:

var1 变量的地址: 0xbfebd5c0
var2 变量的地址: 0xbfebd5b6

什么是指针

指针是一个变量,其值为另一个变量的地址,即,内存方位的直接地址。就像其他变量或常量相同,您有必要在运用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般办法为:

type *var-name;

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

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

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

指针的运用

指针的运用一般包括:

  • 界说一个指针变量
  • 把变量地址赋值给指针
  • 拜访指针变量中可用地址的值
#include <iostream>
using namespace std;
int main ()
{
   int  var = 20;   // 实践变量的声明
   int  *ip;        // 指针变量的声明
   ip = &var;       // 在指针变量中存储 var 的地址
   cout << "变量 var 的值是:";
   cout << var << endl;
   // 输出在指针变量中存储的地址
   cout << "指针 ip 指向的地址是:";
   cout << ip << endl;
   // 拜访指针中地址的值
   cout << "指针 ip 指向的地址存的变量的值是:";
   cout << *ip << endl;
   return 0;
}

运转成果:

变量 var 的值是:20
指针 ip 指向的地址是:0xbfc601ac
指针 ip 指向的地址存的变量的值是:20

空指针

赋为 NULL 值的指针被称为空指针

#include <iostream>
using namespace std;
int main ()
{
 int *ptr = NULL;
 cout << "ptr 的值是 " << ptr ;
 return 0;
}

运转成果:

ptr 的值是 0

内存地址 0 有特别重要的意义,它标明该指针不指向一个可拜访的内存方位。但依照惯例,假如指针包括空值(零值),则假定它不指向任何东西。

如需检查一个空指针,您能够运用 if 句子,如下所示:

if(ptr)     /* 假如 ptr 非空,则完结 */
if(!ptr)    /* 假如 ptr 为空,则完结 */

因而,假如一切未运用的指针都被赋予空值,同时避免运用空指针,就能够避免误用一个未初始化的指针。很多时分,未初始化的变量存有一些废物值,导致程序难以调试。

指针自增自减比较

指针自增自减比较一般用于数组

指针递加

#include <iostream>
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++; // 移动到下一个方位
   }
   return 0;
}

运转成果:

Address of var[0] = 0xbfa088b0
Value of var[0] = 10
Address of var[1] = 0xbfa088b4
Value of var[1] = 100
Address of var[2] = 0xbfa088b8
Value of var[2] = 200

指针比较

#include <iostream>
using namespace std;
const int MAX = 3;
int main ()
{
   int  var[MAX] = {10, 100, 200};
   int  *ptr;
   ptr = var; // 指针中第一个元素的地址
   int i = 0;
   while ( ptr <= &var[MAX - 1] ) // 指针指向是否在数组规模内
   {
      cout << "Address of var[" << i << "] = ";
      cout << ptr << endl;
      cout << "Value of var[" << i << "] = ";
      cout << *ptr << endl;
      // 指向上一个方位
      ptr++;
      i++;
   }
   return 0;
}

运转成果:

Address of var[0] = 0xbfce42d0
Value of var[0] = 10
Address of var[1] = 0xbfce42d4
Value of var[1] = 100
Address of var[2] = 0xbfce42d8
Value of var[2] = 200

指针数组

在这里,把ptr声明为一个数组,由 MAX 个整数指针组成。因而,ptr 中的每个元素,都是一个指向 int 值的指针。下面的实例用到了三个整数,它们将存储在一个指针数组中,如下所示:

#include <iostream>
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

也能够用一个指向字符的指针数组来存储一个字符串列表,如下:

#include <iostream>
using namespace std;
const int MAX = 4;
int main ()
{
 const char *names[MAX] = {
                   "Zara Ali",
                   "Hina Ali",
                   "Nuha Ali",
                   "Sara Ali",
   };
   for (int i = 0; i < MAX; i++)
   {
      cout << "Value of names[" << i << "] = ";
      cout << names[i] << endl;
   }
   return 0;
}

运转成果:

Value of names[0] = Zara Ali
Value of names[1] = Hina Ali
Value of names[2] = Nuha Ali
Value of names[3] = Sara Ali

指向指针的指针(多级直接寻址)

指向指针的指针是一种多级直接寻址的办法,或许说是一个指针链

指针的指针便是将指针的地址寄存在另一个指针里面

一般,一个指针包括一个变量的地址。当咱们界说一个指向指针的指针时,第一个指针包括了第二个指针的地址,第二个指针指向包括实践值的方位

C++ 系列 -- 基础知识

一个指向指针的指针变量有必要如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向 int 类型指针的指针:

int **var;

当一个方针值被一个指针直接指向到另一个指针时,拜访这个值需求运用两个星号运算符,如下面实例所示:

#include <iostream>
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

多级直接寻址的运用场景

  1. 动态内存分配:运用多级指针能够完成动态内存分配,即分配一个指针数组,用于存储多个指针变量的地址,然后再分配每个指针变量所指向的内存空间。
  2. 多维数组:在多维数组中,能够运用多级指针来标明二维三维等多维数组,以便在程序中动态地分配和拜访多维数组。
  3. 数据结构:在数据结构中,能够运用多级指针来标明链表、树等数据结构,以便在程序中动态地增加、删去和修正节点。
  4. 函数指针:在函数指针中,能够运用多级指针来标明指向函数指针的指针,以便在程序中动态地办理函数指针

传递指针给函数

C++ 答应您传递指针给函数,只需求简略地声明函数参数为指针类型即可

下面的实例中,咱们传递一个无符号的 long 型指针给函数,并在函数内改动这个值:

#include <iostream>
#include <ctime>
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;
}

当上面的代码被编译和履行时,它会产生下列成果:

Number of seconds : 1695175690

能承受指针作为参数的函数,也能承受数组作为参数,如下所示:

#include <iostream>
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 类型的部分变量的地址(由于假如不是 static 类型的变量,函数履行完毕后该地址指向的部分变量就被毁掉了,回来出来的这个指针就没有意义了)

现在,让咱们来看下面的函数,它会生成 10 个随机数,并运用标明指针的数组名(即第一个数组元素的地址)来回来它们,详细如下:

#include <iostream>
#include <ctime>
#include <cstdlib>

using namespace std;

// 要生成和回来随机数的函数
int * getRandom( )
{
 static int r[5];

 srand( (unsigned)time( NULL ) ); // 设置种子
 for (int i = 0; i < 5; ++i)
 {
  r[i] = rand();
  cout << r[i] << endl;
 }

 return r;
}

// 要调用上面界说函数的主函数
int main ()
{
 int *p;// 一个指向整数的指针

 p = getRandom();
 for ( int i = 0; i < 5; i++ )
 {
   cout << "*(p + " << i << ") : ";
   cout << *(p + i) << endl;
 }

 return 0;
}

运转成果:

624723190
1468735695
807113585
976495677
613357504
*(p + 0) : 624723190
*(p + 1) : 1468735695
*(p + 2) : 807113585
*(p + 3) : 976495677
*(p + 4) : 613357504

引证

引证的实质是一个已存在变量别号

创立引证

int i = 17;
int& r = i; // 成功创立 i 的引证变量 r

r 能够读作是”一个初始化为 i 的整型引证

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

运转成果:

Value of i : 5
Value of i reference : 5
Value of d : 11.7
Value of d reference : 11.7

把引证作为参数

#include <iostream>
using namespace std;
// 函数声明
void swap(int& x, int& y);
int main ()
{
   // 部分变量声明
   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;
   return 0;
}
// 函数界说
void swap(int& x, int& y)
{
   int temp;
   temp = x; /* 保存地址 x 的值 */
   x = y;    /* 把 y 赋值给 x */
   y = temp; /* 把 x 赋值给 y  */
   return;
}

把引证作为回来值

通过运用引证来代替指针,会使 C++ 程序更容易阅读和保护。C++ 函数能够回来一个引证,办法与回来一个指针相似。

当函数回来一个引证时,则回来一个指向回来值的隐式指针。这样,函数就能够放在赋值句子的左面。例如,请看下面这个简略的程序:

#include <iostream>
using namespace std;
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
double& setValues(int i) {  
   double& ref = vals[i];    
   return ref;   // 回来第 i 个元素的引证,ref 是一个引证变量,ref 引证 vals[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;
}

运转成果:

改动前的值
vals[0] = 10.1
vals[1] = 12.6
vals[2] = 33.1
vals[3] = 24.1
vals[4] = 50
改动后的值
vals[0] = 10.1
vals[1] = 20.23
vals[2] = 33.1
vals[3] = 70.8
vals[4] = 50

当回来一个引证时,要留意被引证的方针不能超出效果域。所以回来一个对部分变量的引证是不合法的,可是,能够回来一个对静态变量的引证。

int& func() {
   int q;
   //! return q; // 在编译时产生过错
   static int x;
   return x;     // 安全,x 在函数效果域外依然是有用的
}

引证和指针的区别

五个主要的不同:

  1. 初始化:引证有必要在界说时进行初始化,指针能够在任何时分进行初始化
  2. 空值:引证不能为空,有必要引证某个现已存在的变量,指针能够为空
  3. 重界说:引证不能被从头界说,即不能让引证引证另一个变量,指针能够被从头界说
  4. 运算符:引证没有自己的运算符,它运用被引证变量的运算符,指针有自己的运算符,如*->
  5. 内存办理:引证不需求进行内存办理,它不会导致内存泄漏和野指针等问题,指针需求进行内存办理,需求手动分配和开释内存

引证和指针的运用场景

  1. 函数参数传递:在函数参数传递中,假如需求修正参数的值,则能够运用指针或引证类型来传递参数。假如参数能够为空,则能够运用指针类型。假如参数不能为空,则能够运用引证类型。例如:
// 运用指针传递参数
void func(int* p) {
    if(p){
        *p = 10;
    }
}
// 运用引证传递参数
void func(int& r) {
    r = 10;
}
  1. 动态内存分配:在动态内存分配中,需求运用指针类型来存储分配的内存地址,以便后续拜访内存中的数据。例如:
// 动态分配内存,回来指针
int* p = new int[10];
// 拜访内存中的数据
p[0] = 1;
  1. 数据结构:在数据结构中,需求运用指针类型来标明数据结构中的节点,以便在程序中动态地增加、删去和修正节点。例如:
// 界说链表节点
struct Node {
    int val;
    Node* next;
};
// 动态分配节点,回来指针
Node* p = new Node;
// 修正节点的值和指针
p->val = 1;
p->next = nullptr;
  1. 运算符重载:在运算符重载中,需求运用引证类型来完成运算符的重载,以便更加直观和安全地拜访数据。例如:
// 界说向量类
class Vector {
public:
    // 重载下标运算符
    int& operator[](int i) {
        return data[i];
    }
private:
    int data[10];
};
// 运用引证拜访向量元素
Vector v;
v[0] = 1;

数组

数组声明

有必要声明数组元素类型、数组变量名、数组巨细

type arrayName [ arraySize ];
如:
double balance[10];

数组初始化

选用花括号 {} 来包括数组

  • 数组巨细有必要大于等于元素个数
  • 数组巨细不填则默许是元素个数
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};

数组元素拜访

double salary = balance[9];

多维数组

初始化

int a[3][4] = {
 {0, 1, 2, 3} ,   /*  初始化索引号为 0 的行 */
 {4, 5, 6, 7} ,   /*  初始化索引号为 1 的行 */
 {8, 9, 10, 11}   /*  初始化索引号为 2 的行 */
};

拜访

int val = a[2][3];

指向数组的指针

double *p;
double runoobAarray[10];
p = runoobAarray;

p = runoobAarray; 标明把第一个元素的地址存储在 p 中,接下来就能够运用 *p*(p+1)*(p+2) 等来拜访数组元素

#include <iostream>
using namespace std;
int main ()
{
   // 带有 5 个元素的双精度浮点型数组
   double runoobAarray[5] = {1000.0, 2.0, 3.4, 17.0, 50.0};
   double *p;
   p = runoobAarray;
   // 输出数组中每个元素的值
   cout << "运用指针的数组值 " << endl; 
   for ( int i = 0; i < 5; i++ )
   {
       cout << "*(p + " << i << ") : ";
       cout << *(p + i) << endl;
   }
   cout << "运用 runoobAarray 作为地址的数组值 " << endl;
   for ( int i = 0; i < 5; i++ )
   {
       cout << "*(runoobAarray + " << i << ") : ";
       cout << *(runoobAarray + i) << endl;
   }
   return 0;
}

运转成果:

运用指针的数组值
*(p + 0) : 1000
*(p + 1) : 2
*(p + 2) : 3.4
*(p + 3) : 17
*(p + 4) : 50
运用 runoobAarray 作为地址的数组值 
*(runoobAarray + 0) : 1000
*(runoobAarray + 1) : 2
*(runoobAarray + 2) : 3.4
*(runoobAarray + 3) : 17
*(runoobAarray + 4) : 50

传递数组给函数

三种函数办法参数的声明办法:

// 办法参数是一个指针:
void myFunction(int *param)
{
}
// 办法参数是一个已界说巨细的数组:
void myFunction(int param[10])
{
}
// 办法参数是一个未界说巨细的数组:
void myFunction(int param[])
{
}

比如:

#include <iostream>
using namespace std;
double getAverage(int arr[], int size); // 函数声明
int main ()
{
   int balance[5] = {1000, 2, 3, 17, 50}; // 带有 5 个元素的整型数组
   double avg;
   avg = getAverage( balance, 5 ); // 传递一个指向数组的指针作为参数
   cout << "平均值是:" << 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;
}

从函数回来数组

由于在函数内部界说的数组归于部分变量,函数履行完后这个部分数组的内存会被开释,回来出来的指针指向的数组现已不存在了,所以不能用部分数组。只能选择用静态数组动态分配数组

静态数组

静态数组便是前面加个 static

运用静态数组需求在函数内部创立一个静态数组,并将其地址回来,例如:

int* myFunction()
{
 static int myArray[3] = {1, 2, 3};
 return myArray;
}

让咱们来看下面的函数,它会生成 10 个随机数,并运用数组来回来它们

#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
// 要生成和回来随机数的函数
int * getRandom( )
{
  static int  r[5];
  // 设置种子
  srand( (unsigned)time( NULL ) );
  for (int i = 0; i < 5; ++i)
  {
    r[i] = rand();
    cout << r[i] << endl;
  }
  return r;
}
// 要调用上面界说函数的主函数
int main ()
{
   // 一个指向整数的指针
   int *p;
   p = getRandom();
   for ( int i = 0; i < 5; i++ )
   {
       cout << "*(p + " << i << ") : ";
       cout << *(p + i) << endl;
   }
   return 0;
}

运转成果:

624723190
1468735695
807113585
976495677
613357504
*(p + 0) : 624723190
*(p + 1) : 1468735695
*(p + 2) : 807113585
*(p + 3) : 976495677
*(p + 4) : 613357504

动态分配数组

在函数内部动态分配的数组在函数履行完毕时不会主动开释,所以需求调用函数的代码负责开释回来的数组

#include <iostream>
using namespace std;
int* createArray(int size) {
  int* arr = new int[size];
  for (int i = 0; i < size; i++) {
    arr[i] = i + 1;
  }
  return arr;
}
int main() {
  int* myArray = createArray(5); // 调用回来数组的函数,得到一个指向数组的指针
  for (int i = 0; i < 5; i++) {
    cout << myArray[i] << " ";
  }
  cout << endl;
  delete[] myArray; // 开释内存
  return 0;
}

运转成果:

1 2 3 4 5

参考内容

  • 菜鸟教程
  • C++入门教程,C++基础教程