C++之旅(学习笔记)第1章 基础

C++之旅(学习笔记)第1章

第1章 基础

1.1 程序

C++是一门编译型语言。源代码必须交由编译器处理生成可执行文件,然后由链接器组装成可执行程序。

一个可执行文件通常是为一个特定的硬件与操作系统组合而制定的,换句话说,它在安卓设备与Windows个人电脑之间是不可移植的,因此,可移植性指的是源代码的可移植性,即源代码可以在多种系统中编译成功,然后运行。

最小的C++程序:

int main(){}

定义一个main函数,不接受任何参数,也不做任何事情。

1.2 新东西

//旧版本
#include 
int main() {
	std::cout << "Hello, World!\n";
}
//C++20版本
import std;
int main() {
    std::cout << "Hello, World!\n";
}

import std;指示编译器去声明标准库变量的存在。如果没有这个声明,std::cout << "Hello, World!\n";将没有意义。但是指令import将所有标准库放进一个单独的std模块还没有成为标准。

1.3 函数

double get(const vector& vec, int index);
//函数类型是:
double(const vector&,int)

对于成员函数来说,类的名称也是函数类型的一部分:

char& String::operator[](int index);
//函数类型是:
char& String::(int)

1.4 类型、变量、运算

  • 前缀0b表示二进制整数字面量,如:0b10101010
  • 前缀0x表示十六进制整数字面量,如:0xBAD12CE3
  • 前缀0表示八进制字面量,如:0334

可以引入单引号(’)作为数字分隔符提升长字面量的可读性

例如:Π的值大约是:3.14159 ‘ 26535 ’ 89793 ‘ 23846 ’ 26433 ‘ 83279 ’ 50288

用十六进制表示就是:0x3.243F ’ 6A88 ‘ 85A3 ’ 08D3

1.4.1 运算

  • 部分操作符的计算顺序是从左向右的:

    x.y、x->y、x(y)、x[y]、x<>y、x&&y、x||y

  • 但赋值符号的计算顺序是从右往左的:

    x += y

1.4.2 初始化

  • 使用=或者{}初始化
double d1 = 2.3;
double d2 {2.3};//等价于double d2 = {2.3}
  • 使用=的形式是C语言传统的方式,如果拿不定主意,就是用更通用的{}列表形式。可以避免隐式类型转换导致的信息丢失
int i1 = 7.8;//i1变成了7(你可能感到意外)
int i2 {7.8};//错误:floating-point to integer conversion
  • 当使用=而不是{}的时候,会进行从double到int及从int到char这样的窄化类型转换。

如果变量的类型可以从初始化符号中推导出来,就无需显示指定类型

auto b = true; 	//bool类型
auto ch = 'x';	//char类型
auto i = 123;	//int类型
auto d = 1.2;	//double类型
...

使用auto声明变量时,作者倾向于使用=符号,因为没有类型转换的风险。当然,偏好使用{}也无伤大雅。

当没有明显的需要显示地指定类型时,一般使用 auto 。

理由如下:

  1. 该定义的作用域较大,我们希望代码的读者清楚地知道其类型。
  2. 初始化表达式的类型(对读者来说)不是显而易见的。
  3. 我们希望明确规定某个变量的范围和精度(如:希望使用double而非float)。

1.5 作用域和生命周期

  • **局部作用域:**在函数或匿名函数中定义的名字叫局部名字,作用域从声明它的地方开始,直到声明语句所在的块结尾。语句块的边界由一对{}决定。函数参数的名字也属于局部名字。
  • **类作用域:**定义在类的内部,不在任何函数、匿名函数、enum class中,可被叫做成员名字(或类成员名字)。作用域从它括起声明的左花括号 { 开始,到对应的右花括号 } 结束。
  • 命名空间作用域:在命名空间内部,并且不再任何函数、匿名函数、enum class中,则把这个名字叫做命名空间成员名字。作用域从声明它的地方开始,到命名空间结束为止。

某些对象也可以没有名字,比如:临时对象或者用new创建的对象:

vector vec;					//vec是全局名字(全局整数动态数组)
void fct(int arg) {					//fct是全局名字(全局函数)arg是局部名字(局部整数参数)
    string motto {"Who dares wins"};//motto是局部名字
    auto p = new Record{"Hume"};	//p指向无名Record对象(由new创建)
    //...
}
struct Record {
  	string name;					//name是Record的成员名字(字符串成员)
    //...
};

一个new创建的对象可以持续“生存”,知道用delete将其销毁。

1.6 常量

C++支持两种不变性:

  • const:“我承诺不修改这个值”,主要用来说明接口,可以用指针或者引用的方式传入函数参数而不用担心被改变。编译器负责强制执行const承诺。const声明的值可以在运行时被计算。
  • constexpr:“请在编译时计算出它的值”,主要用于声明常量,作用是把数据置于只读内存区域(更小概率被破坏),以及提高性能。constexpr的值必须由编译器计算。

例如:

constexpr int dmv = 17;				//dmv是一个命名常量
int var = 17;						//var不是常量
const double sqv = sqrt(var);		//sqv是一个命名常量,可能在运行时计算
double sum(const vector&);	//sum不会修改它的参数
vector v {1.2, 3.4, 4.5};	//v不是常量
const double s1 = sum(v);			//可行:sum(v)在运行时计算
constexpr double s2 = sum(v);		//错误:sum(v)不是一个常量表达式

为了使函数可在常量表达式中使用,这个函数必须被定义为constexpr 或consteval,这样才能在编译期表达式中被计算。

例如:

constexpr double square(double x){return x*x};
constexpr double max1 = 1.4*square(17);	//可行:1.4*square(17)是常量表达式
constexpr double max2 = 1.4*square(var);//错误:var不是常量,所以square(var)不是常量
const double max3 = 1.4*square(var);	//可行:允许在运行时计算

1.7 指针、数组、引用

  • 数组:同类型元素的连续分配序列。
  • 指针:可存放指定类型的对象的地址。

在声明中,[ ]意味着对应类型的数组,*意味着指向对应类型的指针。

char v[6];		//6个字符组成的数组
char *p = &v[3];//p指向v的第4个元素
char x = *p;	//*p代表p指向的对象
  • 前置一元操作符*表示取内容,前置一元操作符&表示取地址,后置一元操作符&表示指向前者的引用。
  • 引用在初始化后就不能再指向其他的对象了。
  • 当用于什么语句时,操作符&*[ ]被称为声明操作符。
T* p	//T*:p是一个指向T的指针
T& r	//T&:r是一个指向T的引用
T f(A)  //T f(A):f是一个函数,接受A类型的参数,返回T类型的结果

1.7.1 空指针

当确实没有对象可指向,我们希望表达出一种“没有对象可用”的含义时,可令指针取值为nullptr。所有指针类型共享同一个nullptr。

在使用指针前检查它是否为空,是明智的行为:

int count_x(const char* p, char x){
    //计算x在p[]中出现的次数
    //假定p指向一个以零结尾的字符数组(或者指向空)
    if(p == nullptr)
        return 0;
    int count = 0;
    for(; *p != 0; ++p)
        if(*p == x)
            ++count;
    return count;
}
  • 假定输入的char*是一个C风格字符串,也就是说,改指针指向一个以零结尾的char数组。字符串字面量中的字符是不可变的。
  • 所以为了以count_x(“Hello!”),这样的格式接收字符串字面量作为参数,因此把第一个参数声明为const char*。
  • 在旧式代码中,常常用0或者NULL代替nullptr,但是,使用nullptr可以消除整数(0或者NULL)与指针(nullptr)之间存在的潜在歧义。
  • 指针可以为空,但引用不能,引用必须指向有效的对象(编译器也假定如此)。当然,有些奇淫巧计可以打破这个规则,但请不要这么左。

1.8 建议

  1. 使用#include 或者 import引入库,可以简化编程。
  2. 函数重载的适用情况是,几个函数的任务相同而处理的参数类型不同;
  3. 如果一个函数可能需要在编译时求值,那么将它声明为constexpr;
  4. 如果一个函数必须在编译时求值,那么将它声明为consteval;
  5. 如果一个函数不允许有副作用,那么就将它声明为constexpr或consteval;
  6. 在声明语句中制定了类型名称(而非使用auto)时,优先使用{}初始化语法;
  7. 使用auto可避免重复输入类型名称;
  8. 在if语句的条件中声明变量时,优先采用隐式检验而不是与0或nullptr进行比较;
  9. 优先使用范围for语句而非使用显示循环变量的传统for语句;
  10. 只对位运算使用unsigned;
  11. 使用nullptr而非0或NULL;

你可能感兴趣的:(C++,c++,学习,笔记,学习方法,程序人生)