C++笔记(一)

Posted on 2023-04-07 23:09  呱呱呱呱叽里呱啦  阅读(25)  评论(0编辑  收藏  举报

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;
}