C++笔记(一)
反复考量之后,还是决定将C++作为我的第二语言以及以后的主力开发语言。
语法基础
基本数据类型
基本有四种类型:
- 整型(修饰符:short、long、signed、unsigned)
- 浮点型(包括float和double,修饰符:long)
- 字符型
- 布尔型
注意数字类型存在有无符号的区别,有无符号的计算参考下面的[补码](# 补码)
变量、常量
#include <iostream>
using namespace std;
int main()
{
// 变量
string color; // 声明变量,标识符基本是通用约束:字母数字下划线,不能以下划线开头,不能使用关键字,大小写敏感
int age; // 其他还有16位short int和32位long int等
double weight; // 还有float和long double等
char c; // 以上多数类型包含有符号和无符号类型
bool b;
color = "三花"; // 首次赋值,初始化变量,变量需要初始化才可以引用,没有其它语言中的默认零值
age = 3;
weight = 4.0;
c = 'a';
b = false;
cout << color << age << weight << c << b << endl;
// 常量
// 整数前缀进制基数0x表示十六进制,0表示八进制,不带前缀默认十进制
// 整数后缀可以是U或者L,U表示无符号,L表示长整数
const double PI = 3.1415926;
cout << PI;
}
作用域
#include <assert.h>
int get_var();
int get_static_var();
int x = 1;
int main()
{
int a = get_var();
int b = get_static_var();
assert(a == 1 && b == 2);
a = get_var();
b = get_static_var();
assert(a == 1 && b == 3);
int x = 0;
assert(::x == 1); // 通过::调用被shadow掉的全局变量
}
int get_var()
{
int a = 0;
a++;
return a;
}
int get_static_var()
{
// 静态局部变量只会初始化一次,生命周期和程序相同
// 全局变量和静态局部变量可以自动初始化,局部变量不会自动初始化
static int b = 1;
b++;
return b;
}
基本运算
#include <assert.h>
int main()
{
int x = 5, y = 2;
// 算数运算符
assert(x / y == 2); // 地板除
assert(5.0 / y == 2.5);
assert(++x == 6); // 当自增/自减符前置时,变量先于当前语句的执行进行自增自减操作并更新值
assert(x-- == 6); // 当自增自减符后置时,变量会在当前语句执行后再进行自增自减操作并更新值
// 关系运算符
// 等于==;不等于!=;大于>;小于<;大于等于>=;小于等于<=
assert(x != y);
// 逻辑运算符
// 与&&;或||;非!
bool t = true, f = false;
assert(t || f);
// 赋值运算符
// 赋值=;加赋+=;减赋-=;乘赋*=;除赋/=;模赋%=;左移后赋值<<=;右移后赋值>>=;按位与后赋值&=;按位异或后赋值^=;按位或后赋值|=
f += 1;
assert(f);
y <<= 1;
assert(y == 4);
// 位运算符
// 按位与&:仅当两个操作数都为1时为1;按位或:仅当两个操作数都为0时为0;按位异或:仅当两个操作数不同时为1;按位取反~
int z = 10;
assert(~z == -11);
// 其他运算符
// 三目运算符:条件语句?真时值:假时值;逗号运算符:顺序执行多个运算,值为最后表达式的值;...
assert(true?1:2 == 1);
assert(sizeof(x) == 4);
assert((x, y, z) == 10);
}
#include <assert.h>
int main()
{
// c++也有条件判断短路
int x = 0;
if (x && ++x) {}
assert(x == 0);
int y = 1;
if (y || ++y) {}
assert(y == 1);
}
#include <iostream>
using namespace std;
int main()
{
float a = 0.1;
float b = 0.0;
cout << a / b << endl; // inf
float c = 0.0;
float d = 0.0;
cout << c / d << endl; // -nan(ind)
}
补码
无符号数表示:按位加权求和即可
有符号数表示:计算机中统一使用补码,补码可以统一符号位和数值域、加减法运算,对于计算机来说,对于不同位(符号位、数值域)上的值的无差别处理可以简化底层硬件设计和实现。
通过补码可以构造出一张映射表,这样减法就可以通过:
减数加上被减数的映射值x得出y,通过映射表得到y的映射值z,z即减法结果
#include <assert.h>
unsigned int B2U(unsigned int num)
{
return (unsigned int)(num);
}
int B2T(int num)
{
return (int)(num);
}
int main()
{
assert(B2U(0xFFFFFFFF) == 4294967295);
assert(B2T(0xFFFFFFFF) == -1);
assert(B2T(0x80000000) == -2147483648);
}
对于有符号数不要使用右移
反码:区分正数和负数,正数正常表示即可,负数保留符号位,将数值域按位取反,然后加1
反码更接近人类思考方式,但不能用。很多时候,如果有多种方案作为计算机运作时的解决方案,首先应该排除更接近人类常规思维方式的方案,因为这种方案很可能不适合计算机发挥性能。
字节序
一个字(32位机器的4个byte或32个bit)以byte存放的方式
大端序:IBM大型机或者网络传输,高位在前,整体和字节内部都有序
小端序:intel兼容机,高位在后,每个字节内部有序
基本结构
顺序结构
略
分支结构
#include <iostream>
using namespace std;
int main()
{
cout << "请输入x:" << endl;
string x;
cin >> x;
if (x == "if")
{
if (1)
{
cout << "if分支" << endl;
}
}
// else if实际上是嵌套的if语句
else if (x == "else-if")
{
cout << "else if分支" << endl;
}
else
{
cout << x << endl;
cout << "else分支" << endl;
}cout << "请输入y:" << endl;
int y;
cin >> y;
switch (y)
{
case 1:
cout << 3 << endl;
break; // 默认是不跳出的
case 2:
cout << 4 << endl;
break;
default:
cout << "default" << endl;
}
}
循环结构
#include <assert.h>
int main()
{
int x = 0;
while (x < 10) {
if (x == 6)
{
x += 2;
continue;
}
else if (x == 8)
{
break;
}
x++;
}
assert(x == 8);
for (int y = 0; y < 10; y++)
{
if (x == 6)
{
x += 2;
continue;
}
else if (x == 8)
{
break;
}
}
assert(x == 8);
// 支持do...while...
// 支持goto
++x;
goto here;
++x;
++x;
++x;
here:
--x;
assert(x == 8);
}
指针
指针是基于基本数据类型的复合数据类型,占8个字节。
#include <iostream>
#include <assert.h>
using namespace std;
void print_p(void* p);
int main()
{
int n = 1;
int* p = &n;
n = 2;
assert(*p == 2);
int a = 1;
const int* pa = &a;
a = 2;
// *pa = 3; // 常量指针,不能通过指针直接修改变量的值,可以重新指向其他变量(也不可通过指针修改值)
assert(*pa == 2);
int b = 1;
int* const pb = &b;
b = 2;
// pb = &a; // 指针常量,可以通过指针修改变量的值,不可以重新指向其他变量
assert(*pb == 2);
// 另外有常指针常量,不可通过指针修改变量的值,也不可重新指向其他变量
print_p(&b);
}
void print_p(void* p)
{
cout << p << "指向的值是:" << *((int*) p) << endl;
}
内存空间
- 内核空间
- 用户空间
- 栈:局部变量、函数参数和返回值(降序分配内存地址)
- 堆:动态开辟内存的变量(升序分配内存地址)
- 数据段:全局变量、静态变量
- 代码段:可执行代码、常量(程序开始运行后不变)
动态分配内存
主要是为了使用更大空间的堆区内存和手动控制内存释放
#include <assert.h>
void settle_mem(int** pp)
{
*pp = new int(6);
}
int main()
{
int* n = new int{ 5 };
*n += 5;
assert(*n == 10);
delete n;
}
二级指针
#include <assert.h>
void settle_mem(int** pp)
{
*pp = new int(6);
}
int main()
{
// 二级指针
int x = 6;
int* px = &x;
int** ppx = &px;
assert(*ppx == &x);
assert(**ppx == x);
// 可以通过二级指针为指针分配内存
int* p;
settle_mem(&p);
assert(*p == 6);
}
空指针
可以用0或者NULL表示空指针,可以用来屏蔽编译错误。
解引用空指针会引起程序崩溃,delete空指针会被忽略。
无论一个指向0、NULL、nullptr,都可以用三者中的任意一个值与指针比较来确认是否是空指针。
#include <assert.h>
int main()
{
// 空指针
int* p = NULL;
assert(p == 0);
assert(p == NULL);
assert(p == nullptr);
}
野指针
主要是指没有初始化的、动态分配的内存已被释放的、自动分配的内存已被回收的或者数组越界的指针,访问野指针可能导致程序崩溃。至于解决办法,就是针对可能出现野指针的情形进行规避,不出现野指针就解决了野指针的问题。
函数指针
#include <iostream>
using namespace std;
int my_add(int a, int b);
int calculate(int (*f)(int, int), int a, int b);
int main()
{
// 函数指针
calculate(my_add, 1, 2);
}
int my_add(int a, int b)
{
return a + b;
}
int calculate(int (*f)(int, int), int a, int b)
{
// 只是演示,实际上这里可以直接访问到全局标识符my_add
int res = f(a, b);
cout << "a=" << a << ", b=" << b << ", res=" << res << endl;
return res;
}
常见容器类型
数组
数组要求数据类型相同。
数组空间在内存中是连续的,数组名多数时候被认为是第0个元素的地址(少数语句中代表其他含义,例如sizeof运算符作用在数组名时将返回数组的字节数),通过数组地址+n可以访问到下标为n的数组元素,数组名是常量,不可修改。
当编译器遇到地址[下标]时会认为是**(地址与下标之和)*
对数组取址将会得到行指针(值与数组第0个元素地址相同),行指针+1将得到下一个行指针即,第0个元素地址+数组字节数。
#include <iostream>
#include <assert.h>
using namespace std;
void print_2d_arr(int rp[][6], int rows);
void print_arr(int arr[], int len);
int comp_asc(const void* p1, const void* p2);
int comp_desc(const void* p1, const void* p2);
int bin_search(int arr[], int len, int target);
int main()
{
// int arr[get_len()]; 表达式必须在编译器可被计算
// int num = 6; int arr[num];表达式应包含常量
const int num = 6; int arr[num];
for (int i = 0; i < 6; i++)
{
arr[i] = i * i;
}
assert(arr[5] == 25);
// 数组支持使用{}声明时直接初始化赋值,并且支持长度推导,支持手动初始化时自动零值
// 数组拷贝
int arr1[num];
memcpy(arr1, arr, sizeof(arr));
// 数组清零
memset(arr, 0, sizeof(arr));
for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
{
assert(arr[i] == 0);
}
// 当编译器遇到 地址[下标] 时会认为是 *(地址与下标之和)
int* p = arr1;
for (int i = 0; i < sizeof(arr1) / sizeof(int); i++)
{
assert(arr1[i] == *(p+i));
}
// 数组排序
int to_sort[6] = { 6, 0, 5, 2, 3, 1 };
//qsort(to_sort, sizeof(to_sort) / sizeof(int), sizeof(int), comp_desc);
//print_arr(to_sort, sizeof(to_sort) / sizeof(int));
// 二分查找
qsort(to_sort, sizeof(to_sort) / sizeof(int), sizeof(int), comp_asc);
print_arr(to_sort, sizeof(to_sort) / sizeof(int));
assert(bin_search(to_sort, sizeof(to_sort) / sizeof(int), 0) == 0);
// 指针长度为8字节
// 对数组取址将会得到行指针(值与数组第0个元素地址相同),行指针+1将得到下一个行指针,即第0个元素地址+数组字节数。
int (*rp)[6] = &arr1;
assert((long long)(rp + 1) == (long long)(&arr1[0] + 6));
// 二维数组
// 二维数组空间在内存中是连续的
int r2d[3][6] = { {1, 2, 3, 4, 5, 6}, {1, 2, 3, 4, 5, 6}, {6, 5, 4, 3, 2, 1} };
print_2d_arr(r2d, sizeof(r2d) / sizeof(int[6]));
cout << (long long)&r2d[0][0] << endl;
cout << (long long)&r2d[2][5] << endl;
assert((long long)&r2d[2][5] - (long long)&r2d[0][0] == (sizeof(r2d) / sizeof(int) - 1) * sizeof(int));
}
void print_2d_arr(int rp[][6], int rows)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < 6; j++)
{
cout << "第" << i << "行第" << j << "列:" << rp[i][j] << endl;
// assert(rp[i][j] == j * j);
}
}
}
void print_arr(int arr[], int len)
{
for (int i = 0; i < len; i++)
{
cout << arr[i] << endl;
}
}
int comp_asc(const void* p1, const void* p2)
{
return *((int*)p1) - *((int*)p2);
}
int comp_desc(const void* p1, const void* p2)
{
return *((int*)p2) - *((int*)p1);
}
int bin_search(int arr[], int len, int target)
{
int front = 0, back = len - 1, mid = 0;
while (front <= back)
{
mid = (front + back) / 2;
if (target == arr[mid]) return mid;
else if (target < arr[mid]) back -= mid;
else front += mid;
}
return -1;
}