C++ Primer学习笔记

C++ Primer Memo

Posted by Kenshin on January 13, 2023
本文字符数:英文731字 | 中文7173字

C++ Primer学习笔记

Startup

  • function definition: {return type, function name, parameter list, function body}
  • iostream:
    • 基础类型: istream, ostream
    • 标准输入输出对象: std::cin, std::cout, std::cerr, std::clog
    • std::endl: manipulator, 结束当前行并将与设备关联的缓冲区中的内容刷到设备中
  • compiling error: syntax error, type error, declaration error
    • edit-compile-debug cycle
  • $ program <infile >outfile
  • class: header, member function/method, (argument)
  • 选用double执行浮点数运算,因为float通常精度不够而且double和float的计算代价相差无几,在某些机器上double运算速度甚至更快。(What about 空间?)

变量和基本类型

基本内置类型

  • 如果两个字符串字面值位置紧邻且仅由空格、缩进和换行符分隔,则它们实际上是一个整体。
  • 转义序列
    • 换行符\n,回车符\r,进纸(换页)符\f
    • 横向制表符\t,纵向制表符\v
    • 报警(响铃)符\a
    • 退格符\b
    • 双引号\",单引号\',反斜符\\,问号\?
  • 指定字面值的类型(literal type)
    字符和字符串字面值
    前缀 含义 类型
    u Unicode16字符 char16_t
    U Unicode32字符 char32_t
    L 宽字符 wchar_t
    u8 UTF-8(仅用于字符串字面常量) char
    整型字面值 浮点型字面值
    后缀 最小匹配类型 后缀 类型
    u or U unsigned f or F float
    l or L long l or L long double
    ll or LL long long

    可以将U与L或LL合在一起使用

变量

  • 当一次定义多个变量时,对象的名字随着定义就马上可以使用了,如在同一条定义语句中可以用先定义的变量值去初始化后定义的其他变量。
    • 初始化和赋值是两个完全不同的操作:初始化是创建变量时赋予一个初始值,赋值是把对象的当前值擦除而以一个新值来替代
    • 列表初始化{}:如果使用列表初始化且初始值存在丢失信息的风险,则编译器将报错,如发生类型转换long double ld = 3.1; int a{ld}; /*(x)*/ int b(ld); /*(√)*/
    • 默认初始化:对于未被显式初始化的内置类型的变量的值由定义的位置决定,定义于任何函数体之外的变量被初始化为0,定义在函数体内部的内置类型变量将不被初始化,此时其值是未定义的,对其访问会引发错误。每个类各自决定其初始化对象的方式。
  • 为支持分离式编译seperate compilation,声明和定义区分,声明使名字为程序所知,定义创建与名字关联的实体。声明规定变量的类型和名字,除此之外定义还申请存储空间,也可能为变量赋初值。
    • 声明在变量名前添加关键字extern且不显式初始化变量,任何包含了显式初始化的声明即成为定义,在函数体内部试图初始化一个extern关键字标记的变量将引发错误
    • 变量只能被定义一次,但是可以被多次声明。
  • 标识符:用户自定义的标识符中不能连续出现两个下划线,也不能以下划线紧连大写字母开头;定义在函数体外的标识符不能以下划线开头。
  • 作用域:嵌套作用域覆盖,作用域操作符::,左侧为空时向全局作用域发出请求

复合类型

  • 引用:左值lvalue引用,右值rvalue引用。引用必须被初始化,定义引用时,程序把引用和初始值绑定bind在一起。取地址符&
    • 引用即别名,引用不是对象,所以不能定义引用的引用
  • 指针:指针本事是一个对象,允许赋值和拷贝,无需在定义时赋初值。同上,不能定义指向引用的指针。解引用符*
    • 指针类型要和指向的对象严格匹配,除了①允许指向常量的指针指向非常量对象②存在继承关系的类,可以将基类的指针或引用绑定到派生类对象上
    • 指针值(即地址)有以下4种状态:指向一个对象;指向紧邻对象所占空间的下一个位置;空指针,意味着指针没有指向任何对象;无效指针,即上述情况之外的其他值
      • 访问后三种状态的指针的值会引发错误,编译器不检查
      • 解引用操作仅适用于确实指向了某个对象的有效指针
    • 空指针:字面值nullptr
      • 预处理变量NULL在头文件cstdlib中定义,值为0。预处理变量不属于命名空间std,由预处理器负责管理,可以直接使用而无需在前面加上std::
      • 尽量使用nullptr,避免使用NULL
      • 把值为0的int变量直接赋给指针也是错误的
    • void*指针可以用于存放任意对象的地址,不能直接操作void*指针所指的对象,可以做的:和别的指针比较,作为函数的输入输出,赋给另一个void*指针
  • 声明:由一个基本数据类型和一组声明符组成,其中类型修饰符(*和&)属于声明符的一部分,不作用于该次定义的全部变量。
  • 离变量名最近的符号对变量的类型有最直接的影响,声明符的其他部分用以确定具体类型是什么。

const限定符:const对象创建后其值就不能更改,因此必须初始化(分为编译时初始化——替换和运行时初始化)

  • const对象仅在文件中有效,如果想要共享,需要在定义和声明前都加上extern关键字。
  • const type &name 对const的引用也应是const,类型转换的常量引用:添加临时量对象。常量引用未对引用的对象本身进行限定,因此可以通过其他方式改变对象的值。
  • type *const name const指针:常量指针必须初始化,*const说明指针是一个常量,而非指向的对象不变。
    • 顶层const:本身是常量;底层const:所指的对象是常量,与复合类型的基本类型部分有关
    • 顶层变量不能改变,底层变量可以改变(const修饰最近的那个基本类型或变量名,如const int和int *const是顶层,const int*是底层)
    • 指针既可以是顶层const也可以是底层const
    • 执行对象的拷贝操作时,顶层const不受影响,底层cosnt要求拷入和拷出的对象具有相同的底层const资格或者可以转换。
  • 综上,从右向左阅读变量声明,关键:*和&只从属于某个声明符而非基本数据类型
  • 常量表达式constexpr由数类型和初始值共同决定,字面值类型literal type:算术类型、指针和引用,自定义类、IO库、string不属于lt
    • constexpr指针初始值必须是nullptr或0或存储于固定地址中的对象
    • constexpr声明中定义的指针,constexpr仅对指针有效(即置为顶层const,与底层const指针区别)

static & const

const只读 static规定作用域和存储方式

一、static修饰类的成员

作用:

  • sataic修饰全局变量:限制全局变量的作用域(使得全局变量只能在本文件中访问)。
  • static修饰局部变量:延长局部变量的生命周期,(比如子函数中初始化只进行一次,后续调用会跳过初始化,使用上次子函数结束时的值)。
  • static修饰函数:和修饰全局变量效果相同,都是限制作用域,使得该函数只能在本文将中访问。

1.1 static修饰类的数据成员

static修饰数据成员:类内声明,类外定义(不能在类内初始化)

结论:类的静态的数据成员属于类,不属于具体的某一个对象,但是所有的对象都可以访问。类的静态的数据成员在编译的时候就已经给分配了内存空间,在创建对象的时候,不会再给静态的数据成员分配内存空间。

类的静态数据成员访问:

1
2
3
<1>.通过对象名.变量名;    
<2>.通过类::变量名;         
注:放在public区域才可直接访问。

作用:可以实现数据的共享

应用:例如实现ID自增,显示在线用户数量

1.​​​​​​​​2 static修饰类的成员函数

和类的静态数据成员访问的方式相同,同样可以通过对象和类名的方式去访问。

作用:实现方法的共享。

1
2
3
静态成员函数可以直接用类来调用,也就没有默认的this指针,(this只能用于非静态成员函数内部)

定位不到对象的数据内存区,访问不了对象的成员。

结论:

1
2
3
静态的成员函数中只能访问类的静态成员。(不知道数据地址)

非静态的成员函数可以访问类的静态成员。(知道数据地址,但可以不使用)

应用:Ctool工具类中一些使用的成员函数的共享。

二、const修饰类的成员

1
2
3
4
5
6
7
8
9
10
11
12
13
const 只读

const int a = 10;

//a的值固定为10,相当于宏定义

const int *p = &a;

//*p的值不能修改,即p指向的空间的内容不能修改,但p的指向可以修改

int * const p = &a;

//p的值不能修改,即p的指向不能修改,但p指向的空间的内容可以修改

2.1 const修饰类的数据成员

1
2
3
4
5
6
7
const修饰数据成员:表示这个数据成员不能修改

如何给const修饰的数据成员初始化:给const修饰的数据成员初始化使用初始化列表,格式:在构造函数首部()后加:变量名(传进来的参数名)

初始化列表只能出现在构造函数中:非const修饰的数据成员也可以在初始化列表中初始化

因为初始化列表有对应的变量名和传参是写在一起的,单纯变量顺序混乱不影响。

2.2 const修饰类的成员函数

格式:void functionName (int a) const{}

const修饰的成员函数本质上const修饰的是成员函数中隐藏参数this指针 const Ctest * const this

this以及this所指的内存中的内容都是不可以修改的

这种情况下,this的指向和this指向的空间的值都不能修改。const修饰的成员函数就只能访问数据成员。

总结:

1
2
3
const修饰的成员函数只能调用const修饰的成员函数

非const修饰的成员函数是可以调用const修饰的成员函数的

说明:

1
2
3
const  Ctest* const this只能再调用const  Ctest* const this,这样才能保证*this不发生改变。

Ctest *const this可以调用const  Ctest* const this,对*this能否改变不做要求。

三、const修饰类的对象

总结:

1
2
3
const修饰的类对象,只能调用const修饰的成员函数。

const修饰成员函数本质上修饰的是this,这样子参数的类型是与普通成员函数类型不一样的,可以有重载。当发生重载时,const对象会调用const函数,普通对象会调用普通函数。(用参数this来匹配)

注:当类对象作为参数引用时,可以加上const表示只提供值,不可修改内容

处理类型

  • 类型别名type alias:使用关键字typedef:typedef 类型 别名;,或别名声明alias declaration:using 别名 = 类型;
    • typedef char *pstring; const pstring cstr = 0;和const char *cstr = 0;是不一样的
    • 前者pstring是char*的别名,const pstring cstr是指向char的常量指针,而const char *cstr是指向常量字符的指针,区别在于基本数据类型
  • auto类型说明符:让编译器通过初始值来推算变量的类型。在一条语句中声明多个变量时,所有变量的基本数据类型必须一样
    • auto使用引用对象的类型作为auto的类型
    • auto一般会忽略掉顶层const,而保留底层const,如果需要推断出的auto类型是顶层const,需要显式地指出cosnt auto
  • decltype类型指示符:选择并返回操作数的数据类型,decltype(f()) name = expr,此过程中编译器分析表达式的返回类型,不实际计算表达式的值
    • 特殊点:引用作为所指对象的别名,只有在decltype中是作为引用类型返回
    • int i = 42, *p = \&i; decltype(*p) c; //错误,*p解引用的结果是int&,c必须初始化
    • decltype (*p + 0) c; //正确,表达式结果类型是int
    • decltype的表达式如果是加上了括号的变量,结果将是引用,即decltype((variable))的结果永远是引用

预处理器preprocessor:确保头文件在多次包含后仍能安全工作

  • #define, #ifdef, #ifndef, #endif
  • 预处理变量无视C语言中关于作用域的规则
  • 整个程序中的预处理变量包括头文件保护符必须唯一,通常是基于头文件中类的名字来构建保护符的名字,一般全部大写

字符串、向量和数组

命名空间的using声明

  • 每个名字都需要独立的using声明:using std::cin; using std::endl;
  • using指示:using namespace std; //不安全
  • 头文件不应包含using声明

string

  • 初始化
    • string s1:默认初始化,s1是空串
    • string s2(s1)string s2 = s1:s2是s1的副本
    • string s3("value")string s3 = "value":s3是字面值的副本(除了最后那个空字符)
    • string s4(n, 'c'):s4初始化为连续n个字符c
    • =是拷贝初始化,()是直接初始化
  • 操作
    • os<<s:将s写入输出流中,返回os
    • is>>s:从is中读取字符串给s,以空格分隔,返回is(忽略开头的空白,即空格符、换行符、制表符等)
    • getline(is, s):从is中读取一行赋给s,返回is(getline读取到换行符为止,读入换行符但是字符串对象不存换行符)
    • s.empty(), s.size()
      • size函数返回的是string::size_type类型,这种标准库类定义的配套类型体现了与机器无关的特性,string::size_type是一个无符号值且足够放下任何string对象的大小
      • 如果使用具有负值的int作比较,如s.size()<n,那么n会自动转换成一个较大的无符号值
    • s[n], s1+s2, s1=s2
    • s1==s2, s1!=s2
    • <, <=, >, >=:字典序比较,对大小写敏感
  • string对象的加法
    • 字符字面值和字符串字面值可以自动转换为string对象,前提是每个+运算符两侧至少有一个是string对象
    • eg. string s1 = "hello" + "," + s0;非法,string s1 = s0 + "hello" + ",";合法
    • 字符串字面值和string是不同的类型
  • cctype
    • isalnum, isalpha, isdigit, isxdigit十六进制数字
    • islower, isupper, tolower, toupper
    • iscntrl控制字符
    • ispunct标点符号(不是控制字符、数字、字母、可打印空白中的一种)
    • isspace空白(空格、横向/纵向制表符、回车符、换行符、进纸符)
    • isprint可打印(空格或可视字符isgraph