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 + + 可以在函数定义时为函数提供默认值,这样在执行的时候就可以不提供实参。
注意:在函数指定默认的时候,要确保没有默认值的在函数列表的左侧,有默认值的在函数列表右侧
#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后,我们的栈和堆空间是这样的。
我们定义了一个对象,和一个指针,指针指向的堆空间地址为0x123,此时p指向的地址上无内容。
这时候,我们用默认的拷贝构造函数来为对象s2赋值,即main函数中的第二行语句。
按照我们的理解来说,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.