Cpp Basic - xapool/xapool.github.io GitHub Wiki

真假

  • C++ 中可以使用 1 0 表示真假,0 为假,非 0 为真
  • C/C++ 中一般返回 0,代表函数执行成功
  • 注意 strcmp 比较字符串时,若字符串相同,返回的是 0 值

数据类型

指针 pointer

  • 存储其它变量地址的变量,称之为指针变量
  • 在声明一个指针变量的时候,在此变量前使用的星号,仅仅是表明其是一个指针变量,并不具有以下意义,因此可以直接使用 & 取地址符进行直接赋值
  • 指针变量在使用的过程中,加不加星号的含义不同
    • 星号后面跟指针名称表示指针所指向内存单元中的内容,即值
    • 不加星号表示指针本身的值,是它自身内存单元中存储的内存地址,用于改变指针所指对象,常与 & 取地址符一起使用
  • 当指针变量是一个 char 类型时,这个指针其实指向的是一个 char 类型数组,是一个数组指针,相当于一个字符串指针,可当做数组一样使用
  • 常量指针和指针常量,容易混淆,不要看中文翻译,直接使用英语
    // 从右向左读,p is a [?] pointer point to ....,* 号读为 point to
    int const *p; // p is a pointer point to const int 
    const int* p; // p is a pointer point to int const,同上
    // 以上,pointer to const,从右往左看,都是 * 号在 const 右边
    int *const p; // p is a const pointer point to int
    const int * const p; // p is a const pointer point to int const
    int const * const p; // p is a const pointer point to const int,同上
    // 以上,const pointer,从右往左看,都是 * 号在 const 左边
    • pointer to const(常量指针),不能通过 *p 来改变所指向对象的值,p 的值可以改变
    • const pointer(指针常量),并不能改变 p 的值,即改变指针所指对象,指针本身是常量,因此需要在声明时就进行初始化,不能执行自增,自减操作。*p 的值可不可以改变得看 point to 的是不是一个常量

数组

  • 给数组同时赋多个值只有在数组初始化时,也就是在声明数组时,才是合法的,以后使用时只能给数组中的单个元素进行赋值
  • 访问数组中元素,中括号也叫位移(offset)操作符,相当于在指针中的地址上加上括号中的数字。例如,下面两个表达式互相等价
    a[5] = 0; // a [offset of 5] = 0
    *(a+5) = 0; // pointed by (a+5) = 0,无论 a 是一个指针还是一个数组名,这两个表达式都合法
  • char 类型数组,还可以使用字符串常量来进行初始化。注意由双引号引起来的字符串末尾总是会被自动加上一个空字符 \0
  • 数组名在大部分情况下会隐式转换为 const pointer(指针常量),来当做指针使用(但并不是一个真正的指针,在代码执时并不像指针一样有自己的内存单元,因为在编译器编译时就直接把数组名转化为了一个固定地址,并在符号表中做好了映射),除了以下几种情况:
    1. sizeof 运算时
    2. & 符号取址时
    3. 用字符串常量初始化字符数组时
    4. C11 的 _Alignof 运算符
  • char 类型数组可以以数组名来用 cout 输出数组全部内容,而普通数组不行。因为 C/C++ 不进行数组的边界检查,数组在内存中存放的只是所有数组元素的值,而不存在一个地方可以表示数组的大小。所以 cout 函数没法知道该输出多少个元素。但字符串则不同,它有一个 \0 用来表示字符串结束,并且每一个元素都是一个字节,cout 看到 \0 就知道输出结束了,但如果没有这个结束符就会出现乱码
  • 指针数组和数组指针,语义上重点在后面的词,类似常量指针和指针常量
    int *a[5]; // 指针数组,每个元素都是一个指针,[]优先级高
    int (*p)[5]; // 数组指针,指针指向一个数组
    char *p = "Hello"; // 同上
    • 指针数组是一个数组,该数组内的每一个元素都是指针,也就是用来存储指针的数组
    • 数组指针一定是一个指针,该指针指向一个数组,也就是数组的第一个元素,该指针的值等于数组第一个元素的地址值,其中存放的是数组第一个元素
  • 指针函数和函数指针
    int *f(int,int) {} // 顾名思义,指针函数是一个函数,该函数的返回类型为整型指针
    int (*f)(int,int); // 函数指针是一个指针,该指针指向的是一个函数,可以通过该指针调用函数

字符数组

操作符

冒号

  • 变量被声明时后面的冒号,是表示该变量占几个 bit 空间
  • 构造函数后面的冒号起分割作用,是类给成员变量进行赋值的方法,用于初始化成员变量,初始化的顺序应与声明的顺序保持一致
  • public:private: 后面的冒号,表示后面定义的所有成员都是公有或私有的,直到下一个 public:private: 出现为止。若不写的话,private: 做为默认,这点不同于 Java
  • 类名后面的冒号是用来定义类的继承,class 派生类名 : 继承方式 基类名,继承方式可为 public、private 和 protected,默认是 public

双冒号

  • 表示 "域操作符",例如,头文件中声明了一个类 A,A 中声明了一个成员函数 void f(),但没有在类的声明里给出 f 的定义(实现),那么在类外定义 f 时, 就要写成 void A::f(),表示这个 f() 函数是类 A 的成员函数
  • 表示引用成员函数及变量,作用域成员运算符,例, System::Math::Sqrt() 相当于 System.Math.Sqrt()
  • 一般还有一种用法,就是直接用在函数名前,表示是调用的是全局函数,非成员函数

点和箭头->

  • C++ 中当定义类对象是指针对象时候,就需要用到 -> 指向类中的成员;当定义一般对象时候时就需要用到 . 指向类中的成员。访问结构体中的成员元素也一样

操作符 &

& 引用声明符

例:

int m; 
int &n = m;

n是m的一个引用(reference),m是被引用物(referent)。n相当于m的别名,对n的任何操作就是对m的操作。所以n既不是m的拷贝,也不是指向m的指针,其实n就是m它自己,在内存中是同一个内存地址

  • 引用的规则
    • 引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)
    • 不能有 NULL 引用,引用必须与合法的存储单元关联(指针则可以是NULL)
    • 一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)
  • C++ 语言中,函数的参数和返回值的传递方式有三种:值传递、指针传递和引用传递。 而 Java 中没有指针传递这一项,只有值传递和引用传递,其中的引用传递类似于 C++ 的指针传递

& 取地址符

& 作为取地址用的时候,常和指针联系在一起,赋值给指针变量,因为指针就是用来存放地址的

OOP

内部类

  • C++ 并没有局部内部类,也就是不能在函数中定义类。并不像 Java 一样区分了普通内部类、静态内部类、匿名内部类,只是一个简单的内部类
  • C++ 的内部类对象没有持有外部类对象的指针,不能访问外部类对象的非静态成员,类似于 Java 的静态内部类,而 Java 的非静态内部类对象有外部类对象的指针,能访问外部类对象的非静态成员
  • C++ 多态的表现和 Java 的上下转型不一样,而是使用强制类型转换操作符。而类似于 Java 的向下转型的写法是为了在一段分配好的空间 上(栈或堆中)调用这个类的构造函数,从而真正的创建一个对象(placement new),可以提高长时间运行的应用程序的性能,减少在堆中进行内存分配的时间

对象的实例化

A a; // 等价于 A a = A()
A a(1); // 等价于 A a = A(1)
A * a = new a(); // 不要忘记不使用时要 delete a

C++ 中对象的实例化可不通过 new 来进行,但使用 new 与否是有区别的。

  • 不使用 new,是在栈上给对象分配内存,不需要手动释放内存
  • 使用 new,是在堆上动态分配内存,使用完须手动 delete 销毁,显然频繁调用场合并不适合 new,就像 new 申请和释放内存一样

GC

  • 销毁对象:当一个对象使用完毕,可以显式的调用类的析构函数进行销毁对象。但此时内存空间不会被释放,以用于其它对象的实例化
  • 释放内存,
    • 如果缓冲区在堆中,那么调用 delete type_pointer 可进行内存的释放
    • 若在栈中,则在其作用域内有效,跳出作用域,内存自动释放

调试

除了使用 IDE 进行调试外,当然也都可以使用命令行进行调试。C 就是使用 gdb 来调试,Java 是使用 jdb 来调试。在 Mac 上 C 还可以使用 lldb 来调试,若使用 gdb 还需要单独安装并签名。在编译时都需要加上 -g 参数,以便程序包含调试信息。最后以调试工具启动程序,gdb test。使用方法类似,都是下断点,运行,调试,具体命令查看各自工具的 help。

头文件

声明类

位运算符

  • 左移运算是将一个二进制位的操作数按指定移动的位数向左移动,移出位被丢弃,右边移出的空位一律补0。在数字没有溢出的前提下,对于正数和负数,左移一位都相当于乘以2的1次方,左移n位就相当于乘以2的n次方
  • 右移运算是将一个二进制位的操作数按指定移动的位数向右移动,移出位被丢弃,左边移出的空位一律补0,或者补符号位,这由不同的机器而定。在使用补码作为机器数的机器中,正数的符号位为0,负数的符号位为1。右移一位相当于除2,右移n位相当于除以2的n次方 例:
1 << 0; //是把 1 的二进制 左移 0 位,结果还是 1,二进制 0000 0001
1 << 1; //是把 1 的二进制 左移 1 位,结果是 2,二进制 0000 0010

QA

  1. 关于变量声明时未进行赋值,这点 C++ 和 Java 一样,做为成员变量,都会给一个默认值。而局部变量,在 C++ 中会是随机的,因为在桟中的内存是反复使用的。需注意在 Java 中若声明时未赋值并且还进行了调用,则会编译报错未初始化变量
  2. 在 C 中,NULL 和 0 的值都是一样的,NULL 常用于指针和对象,0 用于数值。但也有些系统不将 0 地址作为 NULL,而是用其它的地址
  3. 关于栈大小
    • 64位上 linux 内核栈是 16KB,32位上是 8KB
    • 用户态的桟大小,可用 ulimit -a 查看,ulimit -s 设定,Windows 下和编译器配置有关
    • Java 中的栈大小可以通过 JVM 的 -Xss 参数来指定,默认是 1M
    • Java 中每个方法的桟帧大小在编译期就已经确定好了,运行期间并不会改变。C++ 中也可以动态操作,如在栈上实例化对象
    • 没有出口的递归调用,容易造成 StackOverflow
  4. 翻译问题
    • A handle is an abstract reference to a resource. Handle 是对某个资源的抽象引用
    • A handler is an asynchronous callback subroutine. Handler 则是一个异步的回调函数(子程序)
⚠️ **GitHub.com Fallback** ⚠️