C++_Tutorial

重新系统一下学习这门高深的C++编程语言

Reference: https://www.runoob.com/cplusplus/cpp-tutorial.html

简单回顾一下基础:

std::endl和'\n'的区别是什么

"\n" 表示内容为一个回车符的字符串。std::endl 是流操作子,输出的作用和输出 "\n" 类似,但可能略有区别。

std::endl 输出一个换行符,并立即刷新缓冲区。

例如:

std::cout << std::endl;

相当于:

std::cout << '\n' << std::flush;
或者
std::cout << '\n'; std::fflush(stdout);

或者

std::cout << '\n'; std::fflush(stdout);

由于流操作符 << 的重载,对于 '\n' 和 "\n",输出效果相同。

对于有输出缓冲的流(例如cout、clog),如果不手动进行缓冲区刷新操作,将在缓冲区满后自动刷新输出。不过对于 cout 来说(相对于文件输出流等),缓冲一般体现得并不明显。但是必要情况下使用 endl 代替 '\n' 一般是个好习惯。

对于无缓冲的流(例如标准错误输出流cerr),刷新是不必要的,可以直接使用 '\n'。

.h 和 .cpp是什么?
  • .h 文件是头文件,通常包含类的声明、函数原型、宏定义以及其他需要在多个源文件之间共享的代码。头文件中的内容可以被其他源文件包含并使用。通常情况下,头文件中不应该包含具体的实现代码,而是应该包含类、函数、变量的声明。
  • .cpp 文件是源文件,通常包含类的实现、函数的具体实现以及其他代码的具体实现。在 .cpp 文件中,您可以编写类的方法、函数的实现以及其他代码的具体逻辑。

通常情况下,一个类的声明会放在一个 .h 文件中,而该类的实现会放在一个对应的 .cpp 文件中。这种组织方式有助于提高代码的可读性和可维护性,同时也方便了代码的重用和扩展。

为什么要使用 using namespace std?

让我们来逐词理解:

  • using:引用
  • namespace:命名空间(名字空间)
  • std:标准(standard,一个命名空间的名字,cout、endl等都依靠这个命名空间)

简要来说:

命名空间在多人合作的时候很有用,因为你定义了变量 a,别人也定义了变量 a,这样就重复定义了。如果你在自己的命名空间中定义了 a,别人在别人的命名空间中定义了 a,这样就不重复了,比如:

using namespace xx;
using namespace yy;

xx::a 和 yy::a 虽然都叫 a,但是不是同一个变量。

std 是系统标准的命名空间,为了和用户定义的名字不重复,所以它声明在 std 这个命名空间中。另外,这个空间也像一个大包一样,包括了系统所有的支持。

:: 在 C++ 中表示作用域,和所属关系。 :: 是运算符中等级最高的,它分为三种,分别如下:

一、作用域符号:

作用域符号 :: 的前面一般是类名称,后面一般是该类的成员名称,C++ 为例避免不同的类有名称相同的成员而采用作用域的方式进行区分。

例如:A,B 表示两个类,在 A,B 中都有成员 member。

那么:

  •  1、A::member就表示类A中的成员member。
  •  2、B::member就表示类B中的成员member。

二、全局作用域符号:

全局作用域符号:当全局变量在局部函数中与其中某个变量重名,那么就可以用 :: 来区分,例如:

char  zhou;  //全局变量
void  sleep()
{
  char  zhou; //全局变量
  char(局部变量) = char(局部变量)*char(局部变量);
  ::char(全局变量) =::(全局变量) *char(全局变量)
}

三、作用域分解运算符:

:: 是 C++ 里的作用域分解运算符,“比如声明了一个类 A,类 A 里声明了一个成员函数 void f(),但没有在类的声明里给出f的定义,那么在类外定义 f 时,就要写成 voidA::f(),表示这个 f() 函数是类 A 的成员函数。例如:

class CA 
{
public:
  int ca_var;
  int add(int a, int b);
  int add(int a);
}
//那么在实现这个函数时,必须这样写:
int CA::add(int a, int b)
{
  return a + b;
}
//另外,双冒号也常常用于在类变量内部作为当前类实例的元素进行表示,比如:
int CA::add(int a)
{
  return a + ::ca_var;
}
//表示当前类实例中的变量ca_var。
typedef 声明

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

typedef type newname; 

例如,下面的语句会告诉编译器,feet 是 int 的另一个名称:

typedef int feet;

现在,下面的声明是完全合法的,它创建了一个整型变量 distance:

feet distance;
#include<bits/stdc++.h>
using namespace std;
typedef int feet;
typedef long long ll;
int main(){
    feet x = 5;
    ll y = 1e16 + 10;
    cout << x << endl;
    cout << y << endl;
    return 0;
}
//5
//10000000000000010
枚举类型enum

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

如果一个变量只有几种可能的值,可以定义为枚举(enumeration)类型。所谓"枚举"是指将变量的值一一列举出来,变量的值只能在列举出来的值的范围内。

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

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

如果枚举没有初始化, 即省掉"=整型常数"时, 则从第一个标识符开始。

例如,下面的代码定义了一个颜色枚举,变量 c 的类型为 color。最后,c 被赋值为 "blue"。

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

默认情况下,第一个名称的值为 0,第二个名称的值为 1,第三个名称的值为 2,以此类推。但是,您也可以给名称赋予一个特殊的值,只需要添加一个初始值即可。例如,在下面的枚举中,green 的值为 5。

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

在这里,blue 的值为 6,因为默认情况下,每个名称都会比它前面一个名称大 1,但 red 的值依然为 0。

注释:枚举类型不一定在main中定义!!!

#include <bits/stdc++.h>
using namespace std;
enum Weekday {
    Sunday,          // 0
    Monday = 5,
    Tuesday,        // 6
    Wednesday,      // 7
    Thursday,
    Friday,
    Saturday
};

int main(){
    Weekday today = Wednesday;
    cout << today << endl;
    return 0;
}
// output: 7
静态转换(Static Cast)

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

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

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

#include <bits/stdc++.h>
using namespace std;
int main(){
    int x = 9;
    double y = static_cast<double>(x);
    cout << fixed << setprecision(6) << y * 1.0 << endl; // 设置精度保留6位小数
    return 0;
}
// 9.000000
在使用迭代器的时候,也可以用静态类型转换,比如在获得容器的下标的时候
#include<bits/stdc++.h>
using namespace std;
int main(){
    vector<int> nums = {-9, 1100, 43, 4, 300, 20};
    sort(nums.begin(), nums.end());
    //sort后数组为: -9 4 20 43 300 1100
    auto left = lower_bound(nums.begin(), nums.end(), 20);
    auto right = upper_bound(nums.begin(), nums.end(), 1000);
    cout <<"下标是:"<<static_cast<int>(left - nums.begin())<< " 数值是 :" << *left << endl;
    cout <<"下标是:"<<static_cast<int>(right - nums.begin())<< " 数值是 :" << *right << endl;
    //下标是:2 数值是 :20
    //下标是:5 数值是 :1100
    return 0;
}
局部变量和全局变量以及类作用域

在程序中,局部变量和全局变量的名称可以相同。

但是在函数内的局部变量与全局变量是两个独立的变量,互不影响。

下述代码中,全局变量定义了一个int g=99,局部变量定义了一个int g=10,由于这两个g所在的作用域不同,所以各自独立。

#include <bits/stdc++.h>
using namespace std;
// 全局变量声明
int g = 99;

// 函数声明
int func();

int main()
{
    // 局部变量声明
    int g = 10;
    cout << g << endl;      // 10
    int kk = func();
    cout << kk << endl;     // 99
    return 0;
}

// 函数定义
int func()
{
    return g;
}

全局变量和和局部变量同名时,可通过域名在函数中引用到全局变量,不加域名解析则引用局部变量
#include<bits/stdc++.h>
using namespace std;
int a = 100;
int main(){
    int a = 10;
    cout << a << endl;       // 10
    cout << ::a << endl;     // 100
    return 0;
}
#include<bits/stdc++.h>
using namespace std;
int var = -1;       // 全局变量初始化
int main(){
    ::var = 1;      // 全局变量重新赋值
    for(; ::var <= 10; ::var++){
        cout << "全局变量var =" << ::var << endl;
    }
    return 0;
}
//全局变量var =1
//全局变量var =2
//全局变量var =3
//全局变量var =4
//全局变量var =5
//全局变量var =6
//全局变量var =7
//全局变量var =8
//全局变量var =9
//全局变量var =10
类作用域:
这段代码定义了一个名为 Fruit 的类,其中包含一个静态成员变量 price,并将其初始化为 100。静态成员变量是类的所有对象共享的,因此无论创建多少个 Fruit 类的对象,它们都共享同一个 price 变量。

在 main 函数中,首先创建了一个 apple 对象,然后创建了一个 banana 对象。由于 price 是静态变量,apple 和 banana 对象都共享同一个 price 变量,因此无论输出 apple.price 还是 banana.price,最终的输出结果都是 100。
#include<bits/stdc++.h>
using namespace std;
class Fruit{
public:
    static int price;
};

int Fruit::price = 100;

int main(){
    Fruit apple;
    Fruit banana;
    cout << apple.price << endl;     // 100
    cout << banana.price << endl;    // 100
    return 0;
}
如果要建立一个动态变量(即每个对象都有自己的变量而不是共享一个变量),可以将变量声明为类的非静态成员变量,并在每个对象的构造函数中对其进行初始化。这样,每个对象都会有自己的变量。

例如,修改 Fruit 类,将 price 变量声明为非静态成员变量,并在构造函数中初始化:
#include<bits/stdc++.h>
using namespace std;

class Fruit{
public:
    int price;      // 非静态成员变量
    Fruit():price(0){} // 构造函数初始化 price
};

int main(){
    Fruit apple;
    apple.price = 100;
    Fruit banana;
    banana.price = 10;
    cout << apple.price << endl;   // 输出100
    cout << banana.price << endl;  // 输出10
    return 0;
}
常量

整数常量可以是十进制、八进制或十六进制的常量。前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制。

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

下面列举几个整数常量的实例:

#include<bits/stdc++.h>
using namespace std;
int x  = 0xFABCD;       // x 被初始化为十六进制数 0xFABCD,其十进制值为 1022189
int y = 256L;           // y 被初始化为长整型常量 256L,表示为十进制的 256
int z = 412415ul;       // z 被初始化为无符号长整型常量 412415ul,表示为十进制的 412415
const double pi = 3.1415926;
#define Length 100     // 宏定义中不应该以分号结尾
#define Width 0.8
int main(){
    cout << x << endl;
    cout << y << endl;
    cout << z << endl;
    cout << pi << endl;   // 默认情况下 cout 的输出精度是有限的。为了输出更多小数位数,可以使用 std::setprecision 设置输出精度
    cout << fixed << setprecision(10) << pi << endl;
    double area = Length * Width;
    cout << area << endl;
    return 0;
}
C++中逻辑运算符以及杂项运算符

假设变量 A 的值为 60,变量 B 的值为 13,则:A = 0011 1100, B = 0000 1101

运算符描述实例
&按位与操作,按二进制位进行"与"运算。运算规则:0&0=0; 0&1=0; 1&0=0; 1&1=1;(A & B) 将得到 12,即为 0000 1100
|按位或运算符,按二进制位进行"或"运算。运算规则:0|0=0; 0|1=1; 1|0=1; 1|1=1;(A | B) 将得到 61,即为 0011 1101
^异或运算符,按二进制位进行"异或"运算。运算规则:0^0=0; 0^1=1; 1^0=1; 1^1=0;(A ^ B) 将得到 49,即为 0011 0001
~取反运算符,按二进制位进行"取反"运算。运算规则:~1=-2; ~0=-1;(~A ) 将得到 -61,即为 1100 0011,一个有符号二进制数的补码形式。
<<二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。A << 2 将得到 240,即为 1111 0000
>>二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。A >> 2 将得到 15,即为 0000 1111

下表列出了 C++ 支持的其他一些重要的运算符。

运算符描述
sizeofsizeof 运算符返回变量的大小。例如,sizeof(a) 将返回 4,其中 a 是整数。
Condition ? X : Y条件运算符。如果 Condition 为真 ? 则值为 X : 否则值为 Y。
,逗号运算符会顺序执行一系列运算。整个逗号表达式的值是以逗号分隔的列表中的最后一个表达式的值。
.(点)和 ->(箭头)成员运算符用于引用类、结构和共用体的成员。
Cast强制转换运算符把一种数据类型转换为另一种数据类型。例如,int(2.2000) 将返回 2。
&指针运算符 & 返回变量的地址。例如 &a; 将给出变量的实际地址。
*指针运算符 * 指向一个变量。例如,*var; 将指向变量 var。
C++数学运算

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

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

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

序号函数 & 描述
1double cos(double);
该函数返回弧度角(double 型)的余弦。
2double sin(double);
该函数返回弧度角(double 型)的正弦。
3double tan(double);
该函数返回弧度角(double 型)的正切。
4double log(double);
该函数返回参数的自然对数。
5double pow(double, double);
假设第一个参数为 x,第二个参数为 y,则该函数返回 x 的 y 次方。
6double hypot(double, double);
该函数返回两个参数的平方总和的平方根,也就是说,参数为一个直角三角形的两个直角边,函数会返回斜边的长度。
7double sqrt(double);
该函数返回参数的平方根。
8int abs(int);
该函数返回整数的绝对值。
9double fabs(double);
该函数返回任意一个浮点数的绝对值。
10double floor(double);
该函数返回一个小于或等于传入参数的最大整数。
下面是一个例子🌰
#include <iostream>
#include <cmath>
using namespace std;
int main ()
{
   // 数字定义
   short  s = 10;
   int    i = -1000;
   long   l = 100000;
   float  f = 230.47;
   double d = 200.374;
 
   // 数学运算
   cout << "sin(d) :" << sin(d) << endl;
   cout << "abs(i)  :" << abs(i) << endl;
   cout << "floor(d) :" << floor(d) << endl;
   cout << "sqrt(f) :" << sqrt(f) << endl;
   cout << "pow( d, 2) :" << pow(d, 2) << endl;
 
   return 0;
}
//sin(d) :-0.634939
//abs(i)  :1000
//floor(d) :200
//sqrt(f) :15.1812
//pow( d, 2) :40149.7
C++生成随机数

srand((unsigned)time(NULL));:这行代码设置了随机数生成器的种子。time(NULL)返回当前系统时间的秒数,将其转换为unsigned类型作为rand函数的种子,以保证每次运行程序时生成的随机数序列都不同。

#include<bits/stdc++.h>
using namespace std;
int main(){
    srand((unsigned)time(NULL)); // 设置随机数种子
    int j;
    for(int i = 0; i < 10; i++){
        j = rand();
        cout << "随机数:" << j << endl;
    }
    return 0;
}
滚动至顶部