c++基础总结

运算符优先级

优先级 运算符名称 运算符 结合性
1 作用域限定 :: 左结合
2 函数调用 () 左结合
2 下标 [] 左结合
2 成员访问 . 左结合
2 成员访问 -> 左结合
2 算术运算符,自增(后置) ++ 左结合
2 算术运算符,自减(后置) 左结合
2 运行时类型信息 typeid 左结合
2 类型转换 const_cast 左结合
2 类型转换 dynamic_cast 左结合
2 类型转换 reinterpret_cast 左结合
2 类型转换 static_cast 左结合
3 算术运算符,自增(前置) ++ 右结合
3 算术运算符,自减(前置) 右结合
3 地址 & 右结合
3 指针(解引用) * 右结合
3 算术运算符,正号 + 右结合
3 算术运算符,负号 - 右结合
3 位求反 ~ 右结合
3 逻辑运算符,逻辑非 ! 右结合
3 长度 sizeof 右结合
3 类型转换 (类型名) 右结合
3 动态内存分配 new、new[] 右结合
3 动态内存释放 delete、delete[] 右结合
4 成员指针访问 .* 、->* 左结合
5 算数运算符,乘法、除法、求余 *、/、% 左结合
6 算数运算符,加法、减法 +、- 左结合
7 位左移、位右移 <<、>> 左结合
8 关系运算符,小于、大于、小于等于、大于等于 <、>、<=、>= 左结合
9 关系运算符,等于、不等于 ==、!= 左结合
10 位与 & 左结合
11 位异或 ^ 左结合
12 位或 | 左结合
13 逻辑运算符,逻辑与 && 左结合
14 逻辑运算符,逻辑或 || 左结合
15 条件运算符 ? : 右结合
16 赋值 =、*=、/=、%=、+=、-=、<<=、>>=、&=、^=、|= 右结合
17 异常抛出 throw 右结合
18 逗号 左结合

输出指定宽度

我们用setw()函数来指定输出的宽域,该函数包含在头文件 iomanip中, 其中setfill()来设置填充的字符,其里面是字符串。setw默认是右对齐,我们也可以手动设置为左对齐。

#include <iostream>
#include <iomanip>
using namespace std;

int main(void) {
	cout << setw(6) << 123 << endl;                //输出"   123"
	cout << setfill('0') <<setw(6) << 123 << endl; //输出"000123"
	cout << left <<setw(6) << 123 << endl;         //输出"123000" 注,这里cout中setfill已经设置为了'0',可以改回去
	cout << setfill(' ') << left << setw(6) << 123;//输出"123   "
}

输出保留小数位

我们用fixed来让控制宽度为小数部分,再使用setprecison来控制大小。

#include <iostream>
#include <iomanip>
using namespace std;

int main(void) {
	cout << setprecision(3) << fixed << 1.14514 << endl;//输出1.145
}

内联函数

众所周知函数的调用需要花费一定的时间,因此c + + 提供了内联函数机制,可以直接将内联函数的代码复制到调用处。但同样,内联函数会增加占用的内存。所以我们定义内联函数的时候,要尽量的简单和小
对于内联函数,通常不使用函数声明,直接在main函数前定义。

inline double square(double x){
	return x*x;
}

函数默认参数

c + + 可以在函数定义时为函数提供默认值,这样在执行的时候就可以不提供实参。
注意:在函数指定默认的时候,要确保没有默认值的在函数列表的左侧,有默认值的在函数列表右侧

2022-4-17

#include <iostream>
using namespace std;

int superxmy(int x, int y=1,int z=1) {
	return x * 1 + y * 2 + z * 3;
};

int main(void) {
	cout << superxmy(1) << endl;    //输出6   1*1+1*2+1*3
	cout << superxmy(1,2) << endl;  //输出8   1*1+2+2+1*3
	cout << superxmy(1,2,3) << endl;//输出14  1*1+2*2+3*3
}

函数重载

c + + 允许一个作用域中的多个函数具有相同的名字,只要其参数不同,这种就被称为函数重载。重载只和函数名有关,和其返回值无关。例如求一个数的最大值。

#include <iostream>
using namespace std;

int max(int a, int b) {
	return a > b ? a : b;
}
int max(int a, int b,int c) {
	return max(max(a, b), c);
}

int getout(int a) {
	return a;
}
char getout(char a) {
	return a;
}

int main(void) {
	cout << max(1, 2) << endl;    //输出2
	cout << max(1, 2, 3) << endl; //输出3
	cout << getout(1) << endl;    //输出1
	cout << getout('x') << endl;  //输出'x'
	return 0;
}

函数模板

例如函数重载的getout函数,函数的重载几乎相同,只是返回值类型和参数类型不同,这时候可以使用函数模板来定义泛型函数

#include <iostream>
using namespace std;

template<typename T> //模板头
T getout(T a) {
	return a;
}

int main(void) {
	cout << getout(1) << endl;    //输出1
	cout << getout('x') << endl;  //输出'x'
	return 0;
}

异常处理

在程序运行中,考虑到可能会出现的错误,并进行适当的处理来提前终止。

//检验是否输入正数
#include <iostream>
using namespace std;

int main(void) {
	int a = -1,b=2;
	try
	{
		if (a < 0) {
			throw - 1;
		}
		cout << "输入正确" << endl;
	}
	catch (int e)
	{
		cout << "输入错误" << endl;
	}
	return 0;

生成随机数

生成随机数可以使用标准库cstdlib中的rand函数,rand函数返回0~RAND_MAX中的数,RAND_MAX在不同的计算机、操作系统和编译器中值可能不同,一般为32768。

实际上,rand生成的伪随机数一样,rand一般是由“种子”生成,"种子"默认为1,若不改变种子,则会得到相同的随机数序列。可以使用cstlib库中的srand函数来改变种子。(浙江技术考生可以参考vb中的randomize)

#include <iostream>
#include <cstdlib>
using namespace std;

int main(void) {
	unsigned int seed;
	cin >> seed;
	srand(seed);
	for (int i = 0; i < 5; i++) {
		cout << rand() % 9 + 1 << endl;//输出1-9的随机数
	}
	return 0;
}

array数组

c + + 提供了array来替代数组,使用时要添加array头文件。和数组一样,array数组创建后,长度无法再改变。

array<int, 0>arr1;           //生成大小为0的array数组,每个元素初始值随机
array<int, 10>arr2={};        //生成大小为10的array数组,每个元素初始值为0
array<int, 10>arr3={1,2,3};   //生成大小为10的array数组
array<int, 10>arr4=arr3;      //生成大小为10的array数组,和arr3一样

array中存在一些方法(函数),empty判断是否为空,返回bool值。size返回有效元素数量,max_size返回最大元素数量。

cout << arr1.empty() << endl;    //输出1
cout << arr2.max_size() << endl; //输出10
cout << arr3.size() << endl;     //输出10

array中有fill函数和swap函数,用于交换类型、长度相同的array数组。

arr2.fill(1);    //将10个1赋值给arr2数组,每个元素为1
arr2.swap(arr3); //交换两个arr数组
swap(arr2, arr3);//交换两个数组

arr中front函数返回数组的第一个元素值,back函数返回数组的最后一个元素值,[index]可以访问index位置的值,但不检查下表越界。at(index)函数也可以访问,如果越界,会抛出out_of_range异常。(懒得去写例子了)

字符处理函数

需要添加cctype头文件

  • int isdigit(int ch); //判断ch是否十进制
  • int isxdigit(int ch); //判断ch是否十六进制
  • int isalpha(int ch); int isupper(int ch); int is upper(int ch);//判断ch是否是字母,大写字母,小写字母
  • int isalnum(int ch); //判断ch是否为字母或数字
  • int isspace(int ch);//判断是否为空白字符

string字符串

使用string需要包含string头文件。可以使用cin来读入字符串直到遇到空格(空白字符)或回车。若要读入整句话,可以使用gets函数或者getline函数

getline(cin, s1);

c_str函数,将字符串转换为c风格字符串(以’\0’结尾)

str.c_str();

string可以直接使用+或+=来实现字符串拼接。可以使用关系运算符进行比较。

erase函数从字符串删除一个子串,第一个参数是起始位置,第二个参数是长度。
insert函数往字符串插入子串,第一个是插入位置,第二个是要插入的子串。

string s1("superxmy233");
cout << s1.erase(8, 3) << endl;//输出"superxmy"
cout << s1.erase(1) << endl;//省略第二个参数,输出"s"
cout << s1.insert(8, "666") << endl;//输出"superxmy666233"

replace函数替代字符串中的子串,第一个参数是起始位置,第二个参数是长度,第三个是替换的子串

string s1("superxmy233");
cout << s1.replace(8, 3, "666") << endl;//输出"superxmy666"

swap函数交换两个字符串

string s1("superxmy233");
string s2("superxmy666");
s1.swap(s2);
cout << s1 << s2 << endl;//输出"superxmy666superxmy233"

substr函数提取子串,第一个子串起始位置,第二个是长度(可省略第二个参数)。

string s1("superxmy233");
cout << s1.substr(5) << endl;//输出"xmy233"

find函数查找从左到右第一个子串或字符。rfind函数查找从右到左第一个子串或字符。第一个参数是要查找的子串或字符,第二个参数是其实位置,第三个是查找个数(不想演示了)

对象和类

声明类

class MyClass //类名,类头
{
public://公有成员
	MyClass();//构造函数
	~MyClass();//析构函数
	int wobudaoa(){};//成员函数
private://私有成员,外界无法访问,内部可以调用
	
};

一个类可以有多个构造函数,构造函数可以重载析构函数只能有一个,在销毁对象的时候会自动调用,一般用于释放内存。析构函数没有参数。如果没有为对象分配动态空间(new),则对象会在程序结束的时候(return 0)自动释放执行析构函数
静态成员,一个类的所有对象共享静态成员。一个对象改变了静态变量的值,这个类的所有对象都改变了。

自定义异常类

不会讲,这里举个例子。

#include <iostream>
#include <string>
#include <cmath>
using namespace std;

class NegativeNumberException {
public:
	NegativeNumberException(string h) {
		message = h;
	}
	string what() {
		return message;
	}
private:
	string message;
};

double squareRoot(double value) {
	if (value < 0) {
		throw NegativeNumberException("Invalid argument!");
	}
	else {
		return sqrt(value);
	}
}

int main() {
	double value;
	cin >> value;
	try {
		cout << squareRoot(value) << endl;
	}
	catch (NegativeNumberException& ex) {
		cout << ex.what() << endl;
	}
	return 0;
}

拷贝构造函数

众所周知,声明类的时候会自带一个默认的拷贝函数,默认的自定义拷贝函数会把已经存在的对象直接赋值给新的对象。这里的直接赋值并不会执行里面的成员函数,只是简简单单的拷贝粘贴。这种拷贝平常并不会有什么问题,但如果涉及到地址的时候就会出问题

superxmy a(5);
superxmy b(a);//这里就是自动调用了默认的拷贝函数

我们写一个简单的程序来反应这个问题

#include <iostream>
using namespace std;

class superxmy {//定义一个叫superxmy的类
public:
	superxmy() {//定义构造函数
		a = 0;
		p = NULL;//赋初值,让指针一开始指向空
	}
	superxmy(int x) {//构造函数的重载
		a = x;
		p = new int;//分配一个新的空间,让指针指向这个空间
	}
	~superxmy() {//析构函数,用于删除动态内存
		p = NULL;
		delete p;
	}
	int* p;//这里把他放在public是因为懒得去写友好函数了(友好函数之后再说)
private:
	int a;
};

int main() {
	superxmy s1(1);
	superxmy s2(s1);
	*s1.p = 1;       //为s1的p赋值
	cout << *s2.p;   //输出s2的p赋值,输出1!!!!
	return 0;
}

我们分析上面的程序,我们在这个类上定义了一个指针p,这个指针p指向了一个新的我们分配的动态内存空间。我们为s1更新数据的时候,会惊奇的发现s2的p同样也发生了变化。s2原本应该指向一个空间(我们不知道里面具体有啥,因为我们并没有为他赋值),但它却指向了我们为s1提供的动态内存
这里我们就要具体去分析我们的内存。
当我们定义对象s1后,我们的栈和堆空间是这样的。
4-19-1
我们定义了一个对象,和一个指针,指针指向的堆空间地址为0x123,此时p指向的地址上无内容。
这时候,我们用默认的拷贝构造函数来为对象s2赋值,即main函数中的第二行语句。
4-19-2
按照我们的理解来说,s2应该复制一个相同的s1,即p同样指向一个新的地址。但实际上,默认的拷贝构造函数只是简单的执行了内容的复制,并没有执行成员函数 。所以它并没有执行new并没有创建一个新的动态内存空间 。并且s2中的p指针指向的地址复制了s1的p指针指向的地址 ,所以两个对象的P指向的是同一个内存,因此就会出现刚刚的给s1赋值,而s2却同样有效的问题。
为了得到我们想要的结果,我们就需要自定义拷贝构造函数。自定义拷贝函数的语法如下:

类名 (const 类名 & 形参名)

举个例子就是:

superxmy(const superxmy & a);

我们可以把之前的程序改一下

#include <iostream>
using namespace std;
class superxmy {//定义一个叫superxmy的类
public:
	superxmy() {//定义构造函数
		a = 0;
		p = NULL;//赋初值,让指针一开始指向空
	}
	superxmy(int x) {//构造函数的重载
		a = x;
		p = new int;//分配一个新的空间,让指针指向这个空间
	}
//这里是新的---------------------------------------------
	superxmy(const superxmy& Obj) {//定义自定义拷贝函数
		if (Obj.p != NULL) {//如果前面的指针不指向空
			a = Obj.a;
			p = new int;//创建一个新的动态内存空间
		}
		else {//对面指针指向空就赋值啦
			a = 0;
			p = NULL;
		}
	}
//这里是新的---------------------------------------------
	~superxmy() {//析构函数,用于删除动态内存
		p = NULL;
		delete p;
	}
	int* p;//这里把他放在public是因为懒得去写友好函数了(友好函数之后再说)
private:
	int a;
};
int main() {
	superxmy s1(1);
	superxmy s2(s1);
	*s1.p = 1;       //为s1的p赋值
	cout << *s2.p;   //输出s2的p赋值
	return 0;
}

这样就会同时分配两个动态内存空间啦!!!
这种我们也叫作深拷贝

友元函数和友元类

类具有封装性和信息隐藏的特性。就是我们在外面用对象是不能访问到对象内部的private(私有成员)。只能通过一些公有函数来间接访问私有成员。但如果我们用指针的方式来访问内部成员,就可以直接访问私有成员。我们把这样的函数(类)成为友元函数(类)。
我们来举个例子
一般我们要访问内部成员,需要自定义个get函数来获取数据,然后再调用
这里有个bug,之后再举例子
友元类和友元函数差不多,就是把友元函数括号中形参的类改为其他类,即访问另外一个类的私有成员。但是你访问的那个类不能在源文件中被定义,应该在头文件被定义,否则仍然无法访问

继承和多态

这里的内容实在太多,我新开了一个博客来讲这个问题
传送门:c++继承和多态

Q.E.D.