《C++ primer》第二章

一、基本内置类型

如何选择类型:

  • 明确数值不为负时,使用无符号类型
  • 使用int执行整型运算,如果int无法表示,直接使用long long
  • 只有在字符/布尔值时,采用char/bool类型
  • 执行浮点数运算选择double,float精度太小,long double精度过高

类型转换:

  • "bool = 算数类型",算数类型非零则为true,否则为false
  • "算数类型 = bool",bool为false则为0,否则为1
  • "整数 = 浮点数",截断浮点数的小数部分
  • "浮点数 = 整数",小数部分记为0,但可能整数的值超过了浮点数的表达范围(会损失精度)
  • "赋予无符号类型的一个超出其表达范围的值",结果是该值对无符号类型可表达的最大值的取模后的余数
  • "赋予无符号类型的一个超出其表达范围的值",结果未定义,可能会出错

常量表示:

  • 十进制、八进制、十六进制表示整数:20、024、0x14。后缀:u/U为unsigned,l/L为long,ll/LL为long long
  • 浮点数:3.12356、3.12356E0(科学计数法)。后缀:f/F为float,l/L为long double,无后缀则默认为double
  • 字符与字符串:'A'(字符)、"A"(字符串)
  • 转移字符:如"\n"、"\t"等
  • 布尔值:false与ture
  • 指针值:nullptr(为空时)

二、变量

变量定义:

  • int value = 0, sum = value, cnt;//在定义变量时,可以进行初始化,也可以不初始化。当然也可使用已经初始化的变量来初始化当前定义的变量

变量初始化(创建变量时赋予初始值):

  • double price = 19.8, discount = price * 0.16;
  • double salePrice = applyDiscount(price, discount);//可使用函数返回值为变量进行初始化

变量赋值(用新值来替代对象的当前值):

  • price = 20.8;

初始化方式:

  • int i = 0;
  • int i = {0};//列表初始化,当初始值存在丢失的风险时,编译器会报错
  • int i{0};//列表初始化,当初始值存在丢失的风险时,编译器会报错
  • int i(0);

默认初始化:

  • 变量在函数之外,默认初始化为0
  • 变量在函数之内,其值未定义,由改位置原本的值决定
  • 类的对象,由该类决定

变量的声明与定义:

  • 声明:使得名字为程序所知,一个文件想要使用别的文件定义的变量,则必须包含该变量的声明。例如:extern int i;(声明i,不会申请空间)
  • 定义:负责创建于变量名相关联的实体。例如:int i;(声明并定义i,会申请空间)
  • 变量只能被定义一次,但是可以被多次声明。"extern int i = 1;"此语句是定义语句,在函数体内部,不能初始化一个由extern关键字标记的变量

标识符:

  • 有字符、数字和下划线组成,其中数字不能作为开头
  • 用户定义的标识符不能连续出现两个下划线
  • 不能以下划线紧连大写字符开头
  • 不能和关键字相同

变量名作用域:

  • 同一个名字在不同的作用域中可能指向不同实体
  • 名字的有效区域起始于名字的声明语句,以声明语句所在的作用域末端结束
  • 全局作用域:例如main函数和全局变量均具有全局作用域,在整个程序范围内均可使用
  • 块作用域:如果在main函数内部定义一个sum,那么sum的作用域就是从定义开始,到main函数结束
  • 嵌套作用域:被韩寒的作用域称为内层作用域,包含着别的作用域的作用域称为外层作用域,当处于内层作用域时,除非使用作用域操作符,否则外层同名变量会被内层同名变量所屏蔽,例如如下所述:
cpp 复制代码
#include<iostream>

int reused = 42;//变量reused具有全局作用域

int main(void)
{
    int unique = 0;
    std::cout << reused << " " << unique << std::endl;
    int reused = 0;//新建局部变量reused,覆盖了全局变量reused   
    //输出局部变量reused的值,为0
    std::cout << reused << " " << unique << std::endl;
    //输出全局变量reused的值,为42,因为使用了"::"显示访问全局变量
    std::cout << ::reused << " " << unique << std::endl;
    return 0;
}

三、复合类型

复合类型:引用与指针

引用:

  • 引用和对象绑定在一起,而不是将对象的值拷贝给引用。引用必须用对象进行初始化
  • 引用的类型必须与其所引用的对象的类型一致(但有两个例外)
  • 引用本身不是一个对象,不可以定义引用的引用
  • 通过操作引用,就是在操作与引用绑定在一起的对象
cpp 复制代码
int val = 2;                     //定义一个int型变量
int &refval_1 = val;             //定义一个引用refval_1,将其与val绑定在一起
refval_1 = 4;                    //通过修改引用refval_1,从而修改val
int &refval_2 = refval;          //注意,此处并不是定义引用的引用,此语句是正确的,refval_2也与val绑定
int &refval_3 = 10;              //错误,引用类型的初始值必须是一个对象
double &refval_3 = val;          //错误,引用类型的初始化必须是double型

指针:

  • 指针指向另一种类型的对象,存储该对象的地址,指针不是一定要初始化,但是建议定义时进行初始化
  • 指针的类型必须与所指向的对象的类型一致(但有两个例外)
  • 指针本身也是一个对象,允许指针进行拷贝和赋值,并且可以先后指向同种类型的不同对象
  • 通过指针操作其指向的对象时,需要用到解引用符"*",表示取指针的地址处的内容,即得到指向对象的地址,通过操作该地址,从而操作该对象
  • 当指针拥有合法值时,可以用于条件表达式
  • 对于类型相同的两个合法指针,可以使用"=="和'!='来比较两个指针
cpp 复制代码
int val = 2;         //定义并初始化变量val
int *pv_1 = &val;    //定义整型指针pv_1指向val
int *pv_2 = pv_1;    //定义pv_2,通过获取pv_1的值,指向val
*pv_1 = 3;           //通过指针,修改val的值,此时val == 3  

if (pv_1)            //可用于条件表达式
    std::cout << *pv_1 << std::endl;
else 
    std::cout << "the piont is nullptr!" << std::endl;

if (pv_1 == pv_2)    //可用"=="进行比较两个指向相同数据类型的指针
    std::cout << "pv_1 and pv_2 are the same!" << std::endl;
else
    std::cout << "pv_1 and pv_2 are n0t the same!" << std::endl;

指针的指针:

cpp 复制代码
int val = 2;
int *pv = &val;
int **ppv = &pv;   //指向pv,ppv存储pv的地址

指针的引用:

cpp 复制代码
int val = 2;      //定义并初始化整型变量val
int *p;           //定义一个指针p
int *&r = p;      //定义指针p的引用r
r = &val;         //r指向val,此时,p也指向val,因为r与p是绑定在一起的
*r = 0;           //通过操作r,来修改val的值,当然,通过p也可以修改

tips:面对一条比较复杂的指针或引用的声明语句,从右向左阅读有助于弄清楚它的涵义


"&"和"*"。当它们在声明中,分别表示"引用"、"指针"。当它们在运算中,分别表示"取地址"、"取该地址处的内容"

四、const限定符

const限定符

  • const类型的对象,不可以执行非const对象中改变其内容的操作。例如,赋值操作就不允许。并且const对象必须初始化
cpp 复制代码
const int i = get_size();  //正确,运行时初始化
const int j = 42;          //正确,编译时初始化
const int k;               //不正确,没有进行初始化
  • 如果需要在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字,并且在其它文件中用extern进行声明
cpp 复制代码
//file.cc
extern const int bufSize = fcn();

//file.h
extern const int bufSize;
  • 可以定义const的引用,但是不可以通过该引用来修改const所修饰的对象的值,也不可以试图让一个非常量引用指向常量对象
  • 对常量的引用:初始化常量引用时允许任意表达式作为初始值,只要表达式可转化成引用的类型即可(此种情况引用的类型与引用对象的类型可以不一致)。
  • 对const的引用,可以引用一个非const的对象(此时,当引用的对象的值被修改时,const引用的值也随之变化)
cpp 复制代码
int val;
const int &ref_1 = val;            //常量引用可与一个非常量对象绑定
const int sum;
const int &ref_2 = sum;            //常量引用可与一个常量对象绑定
int &ref_3 = sum;                  //错误,非常量引用不可与常量对象绑定

/*接下来才是惊掉下巴的*/
double val_2 = 3.14159;           //此处加不加const,下面的语句均正确
const int &ref_4 = val_2;         //正确,编译器由double类型生成一个临时的整型常量,将ref_4与该临时常量绑定在一起
  • 指向常量的指针:不能修改其指向对象的值,但是,并不要求所指向的对象必须是常量对象(此种情况指针的类型与所指向对象的类型可以不一致)
cpp 复制代码
const int val = 3;             //声明一个常量对象val
int *ptr_1 = &val;             //错误,ptr_1是普通指针,无法指向常量对象
consth int *ptr_2 = &val;      //正确,ptr_2是指向常量的指针,可以指向常量对象
*ptr_2 = 42;                   //错误,不能通过ptr_2修改一个常量对象
int sum = 5;
ptr_2 = &sum;                  //正确,ptr_2是指向常量的指针,但是也可以指向非常量对象
*ptr_2 = 6;                    //正确,因为sum是非常量对象,可以通过ptr_2进行修改
  • 常量指针:必须初始化。初始化后,其值(所指向的地址)将不再改变,但是可以通过该常量指针修改指向的对象
cpp 复制代码
int val, sum;
int *const ptr = &val;
*ptr = 5;                //正确,ptr声明为常量指针,可以通过ptr来修改val的值
ptr = &sum;              //错误,ptr声明为常量指针,不可以修改ptr的指向
  • 指向常量的常量指针:其指向的对象可以是非常量对象(此时对象可以改变)。如果是指向常量对象,则此时指针的值不能改变,指向的对象也不能改变。
cpp 复制代码
int val, sum;
const int *const ptr = &val;
val = 5;                     //正确,因为val并不是常量
*ptr = 5;                    //错误,ptr声明为指向常量的常量指针,不可以通过ptr修改val
ptr = &sum;                  //错误,ptr声明为指向常量的常量指针,不可以修改ptr的指向

/*总结:
 *如果一个指针声明为指向常量的指针,那么可以指向常量对象,也可以指向非常量对象
 *指针的指向不能发生改变
 *如果指向的是常量对象,不可以通过任何形式修改该常量对象
 *如果指向的是非常量对象,可以通过除用该指针间接修改该对象外,可以使用其它方式修改该对象*/
  • 顶层const与底层const:顶层const表示变量本身是一个常量,底层const表示指针指向的对象是一个常量
cpp 复制代码
int val = 0;
int *const p1 = &val;   //不能改变p1的值,为顶层const
const int ci = 42;      //不能改变ci的值,为顶层const
const int *p2 = &ci;    //可以改变p2的值,但是不能该p2指向的对象的值,为底层const
  • 常量表达式:值不会变化并且在编译过程中即可得到计算结果的表达式
  • constexpr:使用constexpr必须满足变量是一个常量,并且该变量必须使用常量表达式或constexpr函数来初始化该变量(变量的类型应该是字面值类型,不可以是自定义的、IO库的类型)
  • 在constexpr声明中,如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指向的对象无关
cpp 复制代码
const int max_files = 20;          //是常量表达式
const int limit = max_files + 1;   //是常量表达式
int staff_size = 27;               //数据类型是普通int型,不是常量表达式
const int sz = get_size();         //sz的具体值需要运行时方可获得,不是常量表达式

constexpr int sz = size();         //如果size()是constexpr函数,该声明是正确的,否则是错误的

const int *ptr = nullptr;          //ptr是一个指向常量的指针
constexpr int *q = nullptr;        //q是一个常量指针,其指向的对象的值可以被修改

constexpr int i = 42;
int j = 0;
//i和j都必须定义在函数体之外,因为函数内的局部变量在编译时仍然未知,在运行时才会被压入栈中
constexpr const int * p = &i;      //p是一个常量指针,指向整型常量i
constexpr int *p1 = &j;            //p1是一个常量指针,指向整数j

五、处理类型

类型别名

使用类型别名,可以提高可读性,有助于程序员清楚地知道使用该类型地真实目的

  • 关键字typedef
cpp 复制代码
typedef double wages;     //wages是double的同义词
typedef wages base, *p;   //base是double的同义词,p是double*的同义词

/*类型的别名与类型名是等价的,只要类型名可以出现的地方,就可以使用类型别名*/
wages hourly, weekly;     //等价于double hourly, weekly;

/*指针、常量与类型别名*/
typedef char *pstring;         //pstring是char *的同义词,即为字符指针
const pstring cstr = nullptr;  //cstr是指向字符的常量指针
const pstring *ps;             //ps是一个指针,它的对象是指向char的常量指针
  • 别名声明------using
cpp 复制代码
using SI = Sales_item;
/*SIs是Sales_item的同义词,
 *此方法用关键字using作为别名声明的开始,其后紧跟别名和等号
 *作用是把等号左侧的名字规定成右侧的类型的别名*/

auto类型说明符

  • 使用auto说明符,能让编译器替我们分析表达式所属的类型,然后推算变量的类型,因此,auto定义的变量必须有初始值
  • 使用auto也能一条语句声明多个变量。一条声明语句只能由一个基本类型,所以该语句中所有变量的初始基本数据类型均应相同
cpp 复制代码
auto i = 0, *p = &i;       //正确,i为int,p为指向int的指针
auto sz = 0, pi = 3.14;    //错误,sz和pi的类型不一致
  • 有时候,auto推断出来的类型并不与初始值的类型完全相同
cpp 复制代码
/*使用引用进行初始化,得到的类型与引用对象的类型一致*/
int i = 0, &r = i;
auto a = r;                 //a的类型为int

/*auto会忽略顶层const,而保留底层const*/
const int ci = i, &cr = ci;
auto b = ci;                //b的类型为int(ci的顶层const被忽略)
auto c = cr;                //c的类型为int(cr是ci的别名,ci是顶层const)
auto d = &i;                //d的类型为int*
auto e = &ci;               //e的类型为指向整数常量的指针

/*希望推断处auto类型的一个顶层const*/
const auto f = ci;         //f是const int

/*设置一个类型为auto的引用时,初始值中的顶层const被保留*/
auto &g = ci;               //g是一个整型常量引用,绑定到ci
const auto &j = 42;         //j是一个常量引用,绑定到字面值

/*&和*只从属于某个声明符,而非基本数据类型的一部分,因此初始值必须是同一类型*/
auto k = ci, &l = i;        //k是整数,l是整数引用
auto &m = ci, *p = &ci;     //m是整数常量的引用,p是指向整数常量的指针

decltype类型指示符

使用decltype可以从表达式中推断出要定义的变量的类型,并且不使用该表达式的值初始化变量。decltype的作用是选择并返回操作数的数据类型,在此过程中,编译器分析表达式得到其类型,但没有真正计算表达式的值

cpp 复制代码
decltype ((f()) sum = x;         //sum的类型是f()的返回值,初始化为x

/*decltype保留顶层const*/
const int ci = 0, &cr = ci;
decltype(ci) x = 0;              //x的类型为const int,初始化为0
decltype(cj) y = x;              //y的类型为const int&,与x绑定
decltype(cj) z;                  //错误,z为const int&,必须初始化

/*如果decltype使用的是表达式时,结果为表达式的返回类型*/
int i = 42, *p = &i, &r = i;
decltype(r + 0) b;               //加法的结果是int,b为int类型
decltype(*p) c;                  //错误,*p为引用,c为int&,必须初始化
/*如果表达式的内容是解引用,则返回类型是引用*/

/*如果变量加上一层或多层括号,编译器会将其视为表达式,返回其引用类型*/
decltype(i), d;                  //d为int类型
decltype((i), e = d;             //e为int&类型

六、自定义数据结构

自定义数据结构

数据结构是一组相关的数据元素组织起来,同时提供操作数据的方法

  • struct:
cpp 复制代码
struct Sales_data {
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
}data_1, data_2;             //data_1、data_2是定义的对象

/*每个对象由自己的一份数据成员的拷贝
 *可以为数据成提供一个类内初始值,在创建类时,类内初始值将用于初始化数据成员
 *没有初始值的成员使用默认初始化*/
  • 编写头文件
cpp 复制代码
/*头文件通常包括只能被定义一次的实体,例如类、const、constexpr变量
 *头文件一旦改变,相关的源文件必须重新编译以获得更新过的声明*/
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include<iostream>
struct Sales_data {
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};
#endif

/*头文件保护符依赖于预处理变量,预处理变量有两种状态:已定义和未定义
 *#define指令把一个名字设为预处理变量
 *#ifndef用于检查预处理变量是否被定义,如果未定义,则进行定义
 *如果已定义,则跳过定义字段
 *预处理变量无视c++中关于作用域的规则,在整个程序中,预处理变量包括头文件描述符必须唯一*/
相关推荐
Hetertopia2 小时前
在Vscode开发QT,完成QT环境的配置
开发语言·qt
傻啦嘿哟2 小时前
动态HTTP代理与静态HTTP代理的区别及HTTP代理的常见用途与类型
开发语言·php
小志开发2 小时前
Java 多态:代码中的通用设计模式
java·开发语言
慕容晓开2 小时前
c++,优先队列
数据结构·c++·算法
索然无味io2 小时前
Python--内置模块和开发规范(上)
开发语言·windows·笔记·python·web安全·网络安全
大麦大麦3 小时前
2025前端最新面试题-安全篇
开发语言·前端·javascript·安全·面试·ecmascript
wyz09233 小时前
python多线程之ThreadLocal 笔记
开发语言·python
Neil__Hu3 小时前
Go的基本语法学习与练习
java·c语言·c++·python·qt·学习·golang
彬sir哥3 小时前
水仙花数(华为OD)
java·c语言·javascript·c++·python·算法
Eugene__Chen3 小时前
java常见面试01
java·开发语言·面试