C++学习笔记--菜鸟教程

VS2015编译按ctrl+F5
网页地址菜鸟教程

“\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’。


真正的开发过程中, 尽量避免使用 using namespace std; 等直接引入整个命名空间,否则会因为命名空间污染导致很多不必要的问题, 比如自己写的某个函数,名称正好和 std 中的一样, 编译器会不知道使用哪一个, 引起编译报错, 建议使用:

std::cout << "Hello World" << std::endl;

等直接由命名空间组合起来的全称。


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

using namespace xx;
using namespace yy;

xx::a 和 yy::a 虽然都叫 a,但是不是同一个变量。std 是系统标准的命名空间,为了和用户定义的名字不重复,所以它声明在 std 这个命名空间中。另外,这个空间也像一个大包一样,包括了系统所有的支持。


cout 流速度较慢,如果速度过慢可以用 <stdio.h> 库中的 printf() 格式化输出函数,不需要 using namespace std;。它的示例为:

#include <stdio.h>
int main(){
    printf("Hello World!\n");
    return 0;
}

C++基本语法

基本等同于C;
头文件:include
新增了命名空间 namespace
输出可以使用cout流

C++注释

/* 这是注释 */ 

/* C++ 注释也可以 
* 跨行 
*/

//到行末


#if 0
   code //不执行,等同于注释,改为 #if 1 调试时执行
#endif 

C++数据类型

长度

类型 长度
char 1个字节
int 4个字节
unsigned int 4字节
long int 8字节
float 4个字节
double 8个字节

typedef

typedef 为一个已有的类型取一个新的名字。


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


size_t 是一种 整型 类型,里面保存的是一个整数,就像 int, long 那样。这种整数用来记录一个大小(size)。size_t 的全称应该是 size type,就是说一种用来记录大小的数据类型
通常我们用 sizeof(XXX) 操作,这个操作所得到的结果就是 size_t 类型。因为 size_t 类型的数据其实是保存了一个整数,所以它也可以做加减乘除,也可以转化为 int 并赋值给 int 类型的变量。
例如:typedef int size_t; 定义了 size_t 为整型。


typedef 与 #define 的区别:

  1. 执行时间不同
    关键字 typedef 在编译阶段有效,由于是在编译阶段,因此 typedef 有类型检查的功能。
    #define 则是宏定义,发生在预处理阶段,也就是编译之前,它只进行简单而机械的字符串替换,而不进行任何检查。
  2. 功能有差异
    typedef 用来定义类型的别名,定义与平台无关的数据类型,与 struct 的结合使用等。
    #define 不只是可以为类型取别名,还可以定义常量、变量、编译开关等。
  3. 作用域不同
    #define 没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用。而 typedef 有自己的作用域。

枚举类型

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

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

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

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

C++变量类型

bool char int float double void wchar_t 枚举 指针 数组 数据结构 类
可以使用 extern 关键字在任何地方声明一个变量。虽然您可以在 C++ 程序中多次声明一个变量,但变量只能在某个文件、函数或代码块中被定义一次。
同样的,在函数声明时,提供一个函数名,而函数的实际定义则可以在任何地方进行。定义包含了声明,但是声明不包含定义。


C/C++ 编译 cpp 文件是从上往下编译,所以 main 函数里面调用其他函数时,如果其他函数在 main 函数的下面,则要在 main 函数上面先声明这个函数。或者把 main 函数放在最下面,这个不仅限于 main 函数,其他函数的调用都是如此。被调用的函数要在调用的函数之前声明。


变量的类型间是可以互相转换的,转换又分为自动转换和强制转换。
自动转换规则

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

强制转换规则

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


C++作用域

作用域是程序的一个区域,一般来说有三个地方可以定义变量:

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

在程序中,局部变量和全局变量的名称可以相同,但是在函数内,局部变量的值会覆盖全局变量的值。

全局变量默认初始值

数据类型 初始默认值
int 0
char ‘\0’
float 0
double 0
pointer NULL

在一个函数体内可以存在重名的变量,前提是它们的作用域不同。例如代码块内定义的同名变量在块执行结束后释放。


存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和 static 变量,只不过和全局变量比起来,static 可以控制变量的可见范围,说到底 static 还是用来隐藏的。虽然这种用法不常见。
PS:如果作为 static 局部变量在函数内定义,它的生存期为整个源程序,但是其作用域仍与自动变量相同,只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。
例如static int a = 10,在函数内部定义,那么下次进入函数,a为上次操作之后的值,效果等同于全局变量。但是函数中的a并不能在函数外部被调用。
静态变量和全局变量的区别在于全局变量对所有的函数都是可见的,而静态局部变量只对定义自己的函数体始终可见。
全局变量只需在一个源文件中定义,就可以作用于所有的源文件。当然,其他不包含全局变量定义的源文件需要用extern 关键字再次声明这个全局变量。
静态全局变量也具有全局作用域,它与全局变量的区别在于如果程序包含多个文件的话,它作用于定义它的文件里,不能作用到其它文件里,即被static关键字修饰过的变量具有文件作用域。这样即使两个不同的源文件都定义了相同名字的静态全局变量,它们也是不同的变量。
Tips:

  1. 若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;
  2. 若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;
  3. 设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题,因为他们都放在静态数据存储区,全局可见;
  4. 如果我们需要一个可重入的函数,那么,我们一定要避免函数中使用static变量(这样的函数被称为:带“内部存储器”功能的的函数)
  5. 函数中必须要使用static变量情况:比如当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。

静态函数:在函数的返回类型前加上static关键字,函数即被定义为静态函数。静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。如果在一个源文件中定义的函数,只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用,这种函数也称为内部函数。定义一个内部函数,只需在函数类型前再加一个“static”关键字即可。


C++常量

定义常量

#define identifier value
const type variable = value;

定义成 const 后的常量,程序对其中只能读不能修改。
宏定义结束不加分号
宏定义 #define 和常量 const 的区别

  1. 类型和安全检查不同
    宏定义是字符替换,没有数据类型的区别,同时这种替换没有类型安全检查,可能产生边际效应等错误;
    const常量是常量的声明,有类型区别,需要在编译阶段进行类型检查
  2. 编译器处理不同
    宏定义是一个”编译时”概念,在预处理阶段展开,不能对宏定义进行调试,生命周期结束与编译时期;const常量是一个”运行时”概念,在程序运行使用,类似于一个只读行数据
  3. 存储方式不同
    宏定义是直接替换,不会分配内存,存储与程序的代码段中;const常量需要进行内存分配,存储与程序的数据段中
  4. 定义域不同
    宏定义不受定义域限制;const常量局限于代码块内
  5. 是否可以做函数参数
    宏定义不能作为参数传递给函数const常量可以在函数的参数列表中出现
  6. 定义后能否取消
    宏定义可以通过#undef来使之前的宏定义失效const常量定义后将在定义域内永久有效

const char*, char const*, char*const 的区别


C++修饰符类型

在int、char、double前田间修饰符,包括signed、unsigned、long、short,在修饰int时,int可以省略;

short int i; // 有符号短整数 
short unsigned int j; // 无符号短整数

对于无符号化为有符号的位数运算,采取 N-2^n 的计算方法,n 取决于定义的数据类型 int、short、char、long int 等等,N 为无符号数的数值,例如文中的 N=50000,short 为 16 位,计算方法为 50000-2^16 得到 -15536。


16 位整数(短整数)的情况下,十进制 50000 就是二进制 11000011 01010000 但在有符号的情况下,二进制最左边的 1,代表这整个数字是负数但是电脑是以补码形式来表示数字的,要获得原本的数字,首先要把整个二进制数 - 11100001101010000 - 1 = ‭1100001101001111‬ 然后,在把答案取反码 not ‭1100001101001111‬ = ‭0011110010110000‬ 把最终答案变成十进制,就是 15536 所以,一开始的二进制数 11000011 01010000,在有符号的情况下代表的就是 -15536。


C++ 存储类

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

存储类 说明
auto auto 关键字用于两种情况:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。
register register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)。
static static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内
extern extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 ‘extern’ 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。可以这么理解,extern 是用来在另一个文件中声明一个全局变量或函数。extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候。
thread_local 使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。thread_local 说明符可以与 static 或 extern 合并。可以将 thread_local 仅应用于数据声明和定义,thread_local 不能用于函数声明或定义

静态局部变量有以下特点:

  • 该变量在全局数据区分配内存;
  • 静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
  • 静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
  • 它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束;

  1. 寄存器存在于CPU中,速度很快,数目有限存储器就是内存,速度稍慢,但数量很大。计算机做运算时,必须将数据读入寄存器才能运算。
  2. 存储器包括寄存器,存储器有 ROM 和 RAM

C++运算符

算术运算符

A =10, B =20.

运算符 描述 实例
+ 把两个操作数相加 A + B 将得到 30
- 从第一个操作数中减去第二个操作数 A - B 将得到 -10
* 把两个操作数相乘 A * B 将得到 200
/ 分子除以分母 B / A 将得到 2
% 取模运算符,整除后的余数 B % A 将得到 0
++ 自增运算符,整数值增加 1 A++ 将得到 11
自减运算符,整数值减少 1 A– 将得到 9

++d 是先加,先对 d 的值加 1,再使用 d 的值执行该行命令。
d++ 是后加,先使用 d 的值执行该行命令,执行完后再对 d 的值加 1。


关系运算符

A =10, B =20.

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

逻辑运算符

A =1, B =0.

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

位运算符

A = 60 = (0011 1100)
B = 13 = (0000 1101)

运算符 描述 实例
& 如果同时存在于两个操作数中,二进制 AND 运算符复制一位到结果中。 (A & B) 将得到 12,即为 0000 1100
| 如果存在于任一操作数中,二进制 OR 运算符复制一位到结果中。 (A | B) 将得到 61,即为 0011 1101
^ 如果存在于其中一个操作数中但不同时存在于两个操作数中,二进制异或运算符复制一位到结果中。 (A ^ B) 将得到 49,即为 0011 0001
~ 二进制补码运算符是一元运算符,具有”翻转”位效果,即0变成1,1变成0。 (~A ) 将得到 -61,即为 1100 0011
<< 二进制左移运算符。左操作数的值向左移动右操作数指定的位数。 A << 2 将得到 240,即为 1111 0000
>> 二进制右移运算符。左操作数的值向右移动右操作数指定的位数。 A >> 2 将得到 15,即为 0000 1111

赋值运算符

运算符 描述 实例
= 简单的赋值运算符,把右边操作数的值赋给左边操作数 C = A + B 将把 A + B 的值赋给 C
+= 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 C += A 相当于 C = C + A
-= 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 C -= A 相当于 C = C - A
*= 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 C *= A 相当于 C = C * A
/= 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 C /= A 相当于 C = C / A
%= 求模且赋值运算符,求两个操作数的模赋值给左边操作数 C %= A 相当于 C = C % A
<<= 左移且赋值运算符 C <<= 2 等同于 C = C << 2
>>= 右移且赋值运算符 C >>= 2 等同于 C = C >> 2
&= 按位与且赋值运算符 C &= 2 等同于 C = C & 2
^= 按位异或且赋值运算符 C ^= 2 等同于 C = C ^ 2
|= 按位或且赋值运算符 C |= 2 等同于 C = C | 2

杂项运算符

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

运算优先级

条件 ?: 从右到左

C++类与对象

定义类

C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心特性,通常被称为用户定义的类型。
类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。类中的数据和方法称为类的成员。函数在一个类中被称为类的成员。
类定义是以关键字 class 开头,后跟类的名称。类的主体是包含在一对花括号中。类定义后必须跟着一个分号或一个声明列表。例如,我们使用关键字 class 定义 Box 数据类型,如下所示:

class Box 
{ 
    public: 
        double length; // 盒子的长度 
        double breadth; // 盒子的宽度 
        double height; // 盒子的高度 
 };

关键字 public 确定了类成员的访问属性。在类对象作用域内,公共成员在类的外部是可访问的。也可以指定类的成员为 private 或 protected。

实例化对象

Box Box1; // 声明 Box1,类型为 Box 
Box Box2; // 声明 Box2,类型为 Box