目录
指向常量的指针变量(指针指向的内容不可以修改,但指向的地址可以修改)
二进制码
原码:用最高位表示数的符号,0表示正数,1表示负数
原码表示0由两种形式 +0=00000 -0=10000;
反码:正数反码与原码相同,负数反码符号位相同其他位取反
补码:正数相同,负数反码末尾加1 表示范围:-128-127(若字长为8)
补码表示0是唯一的,0=00000
负数的补码还原成原码为 除符号位按位取反,然后最低位加一
特别地八位补码比原码多表示一个-128=10000000
BCD码
bcd码是没有进制意义的,实际上以四位二进制码代表一个十进制数,然而十六进制正好是四位,所以十进制xxxx=十六进制xxxxH.
ASCII码
最大只能输出到char a=128;
基本流里的一些格式控制语句
int main()
{
cout.fill('*');
cout.width(10);//c++默认右对齐
cout << 123.45 << endl;//格式控制仅在紧随其后的一个语句有效
cout << 123.45 << endl;
cout.width(4);
cout << 123.45 << endl;//如果已有的输出数量大于设定宽度,依旧全部输出
}
优先级与结合律问题
C++ 运算符优先级_CSDN1HAO的博客-CSDN博客_c++运算符优先级
- ()括号 .成员引用 ->成员选择(自左向右)
- -负号 (类型)强转 ++,--自增自减 *取值 &取地址(自右向左)
- /除 *乘 %取余
- +加 -减
- >,>=,<,<=关系运算符
- ==,!=判断相等运算符
- && 逻辑与
- || 逻辑或
- ?: 条件运算符
- +=,-=,=赋值运算符(从右往左)
- ,逗号运算符
switch语句
int main()
{
int r;
cin >> r;
switch (r)
{
case 1: cout << 1 << endl;
case 2: cout << 2 << endl;
case 3:cout << 3 << endl;
default:cout << 4 << endl;
}
switch的证明
}
如果不加break;会根据初始分支一个个的输出
后置自增运算符
int main()
{
int b = 0;
if (b++ && 1)
cout << 1 << endl;//先逻辑与再自增
if (b++ > 0)
cout << 1 << endl;//先比较再自增
}
无论对于逻辑与或 逻辑比较 输出,后置自增都是提取地址里的原内容直接进行操作
然后再是进行自增赋值
原理是因为自增操作的时候会自动创建一个副本空间,先将自增的结果保存到这个空间,当执行完地址原内容的操作后,再将这个值赋予给地址。
iomanip库
详细见这篇博文:
setw设置字段长度包括整数和小数
float a = 1.322211;
cout << setw(6) << a << endl;
//1.32222
setprecision 保留六位有效数字
float a = 1.3222114;
cout << setprecision(6) << a << endl;
//1.32221
stdlib库和ctime库
C++提供产生随机数的函数rand(),返回在0-32767之间的随机整数
自动种子为1,每次执行是相同的,所以需要利用srand()设好随机数种子
可调用time()函数(ctime库)得到当前系统时间,并将该时间作为随机种子
那我们如何引入变化的种子呢?一般来说,我们会使用time(NULL)或time(0)来表示变化的种子,time(0)的返回的是从1970 UTC Jan 1 00:00到当前时刻的秒数
srand(time(0));
cout << rand() << endl;
for循环的次数问题
for(int i =0;i<n;i++)
对于局部变量i,这个循环会执行n次,最终i消亡前的值为n
for(int i=0;i<=n;i++)
对于局部变量i,这个循环会执行n+1次,最终i消亡前的值为n+1
for(int i=n;i>0;i--)
对于局部变量i,这个循环会执行n次,最终i消亡前的值为0
for(int i=n;i>=0;i--)
对于局部变量i,这个循环会执行n+1次,最终i消亡前的值为-1
for(int i=0;i<n;i++)
for(int i=0;i<n;++i)
无论是i++还是++i都先进行自增再与判断条件进行比较,最终消亡前为n
递归函数调用求n层三柱汉诺塔
递归调用的本质其实是将原问题转化为一个新问题
并且原问题和新问题之间有着相同的解决办法
问题的规模越来越小,直到转化出来的问题是一个有已知解的问题
汉诺塔的原理详见
(要想解决n层汉诺塔的问题,先解决好上方n-1层的问题)
解决的方法都是一样的!
先从n=1想起:
对于一个碟片在A上,我们只需要将它直接移动到C
所以这个可以作为已知解(地基)
再从n=2思考方法:
解决问题的办法我们不妨来看两个碟片的情形
我们先将临时柱子(B)看作是临时目标柱,A为原柱
hanoi(1,a,c,b)
然后再将剩余的一个碟片移动到真正的目标柱
move(a,c)
最后将B看作是原柱,C为目标柱进行移动
hanoi(1,b,a,c)
大功告成!
以此类推,对于三个碟片的情形
我们只需要先将上方两个碟片先从原柱(A)移动到临时目标柱(B)
再进行第三个碟片从A到C的操作
最后再将两个碟片从B移动到C就可以啦!
最终得到解决n个碟片的方法,即解决n-1个碟片的子问题,以此继续递归下去,直到n=1,变成已知问题求解即可
代码段
汉罗塔
int val = 0;
void move(char a,char c)
{
cout << a << "-->" << c << endl;
val ++;
}
void hanoi(int n, char a, char b, char c)
{
if (n == 1)
move(a, c);
else
{
hanoi(n - 1, a, c, b);
move(a, c);
hanoi(n - 1, b, a, c);
}
}
int main()
{
int m;
cout << "Enter the number of diskes:";
cin >> m;
cout << "the steps to moving" << m << "diskes:" << endl;
hanoi(m, 'A', 'B', 'C');
cout << val;
return 0;
}
内联函数与重载函数
内联函数
在编译时将函数体嵌入到每个内联函数的调用处
这样省去了参数传递,系统栈的保护和恢复等时间开销!
内联函数中一般不能含有循环语句和switch语句
inline void solve()
重载函数
仅返回值不同时,不能定义为重载函数!
重载函数必须具有不同的参数个数或不同的参数类型
带有默认参数的函数
默认参数相当于当没有传入实参的时候
直接用默认参数求解并返回值
注意以下几点:
1.不可以靠左边缺省
int area(int long=4,int width)//错误
2.函数原型说明时可以不加变量名
float v(float,float=10,float=20)
全局变量,局部变量与自动变量
全局变量
默认初始化值为0(包括数组置0)
在块作用域内可以通过作用域运算符“::”
来引用与局部变量同名的全局变量
局部变量与全局变量的分区
自动变量
由于局部变量默认为自动类型变量,因此在说明局部变量时,不用auto修饰
void s(void)
{
int x;//auto int x
auto int y;
}
static局部变量
用static修饰的局部变量。在程序开始执行时,获得所分配的内存,生存期全程。
只在函数调用时有用
并且初值为0
static int a = 20;
int main()
{
static int a = 30;
cout <<:: a;//20
cout << a;//30
}
register局部变量
用register修饰的局部变量,尽可能直接分配到cpu的寄存器中,提高存取速度
一般用作循环指针!
static和全局变量不能定义为register变量
extern全局变量
一般以下两种说明全局变量的情况需要使用extern修饰
1.同一文件中,全局变量使用在前,定义在后
2.多文件组成程序,一个源程序文件中定义的全局变量要被其他源程序文件引用时
static全局变量
对于多文件程序来说,如果将全局变量用static来修饰,则该变量只能用于当前文件,能有效避免重名的问题
函数模板
Template<模板形参表>返回类型 函数名(形参列表)
{
函数体;
}
编译预处理
宏定义
#define 标识符 字符串
例如:#define price 30(宏定义的内容是(30)被认为是字符串,标识符称为“宏名”)
宏定义与函数声明联动
int x = 1;
int f(int);
int main()
{
cout << f(x); return 0;
}
#define x 2
int f(int y)
{
return x + y;
}//这里的x指宏定义中的x
注意:
带有参数的宏定义 如#define mul(z) z*z
mul(3+2)
当字符串内容里无括号(z)时,实际表达的意思是3+2*3+2
注意:宏展开只是物理替换,不做语法检查,后面不加分号;
#define命令出现在函数的外面,所以可以用#undef命令来终止宏定义的作用域
条件编译
#ifdef 标识符
程序段1
#else
程序段2
#end if
当标识符被定义过,则对程序段1进行编译,否则编译程序段2
#ifndef 标识符
程序段1
#else
程序段2
#end if
当标识符没有被定义过,则对程序段1编译,否则编译程序段2
二维数组的初始化
int a[ ][3] = { {1,2,3},{4,5} };//正确
int a[3][ ] = { {1,2,3},{4,5} };//错误
字符串数组
字符串数组输入
char a[5] = { "abc " };
cout << a[3] << endl;
char a[2]='a';//错误,不接受单引号
空格也作为字符被传入
通常使用getline函数传入字符串
int main()
{
char a[4];
cin.getline(a,4);
cout << a;
char str[] = 'a';
}
//第二个参数代表输入4个字符,最后一个字符为'\0'
所以输出只有三个字符
字符串数组函数
strcat(s1,s2)合并字符串(防止溢出)
strcpy(s1,“s2”)字符串复制(包括‘\0’都会从s2复制到s1)
strcmp(s1,s2)字符串比较(逐个比较,若不同输出-1,遇到'\0'停止并输出0)
strlen(s1)计算字符串的有效长度(不包括'\0')
注意:
char a[100] = "abcffffffffff";
char b[10] = "abcDFR";
cout << strcpy(a, b);
cout << a;
//abcDFRabcDFR
直接输出str函数 代表着直接输出 复制 合并 后的结果
指针
基本概念
内存单元:可直接访问的计算机内存的最小单元,通常为1字节
存储单元:变量存放的连续的内存单元
地址:每个内存单元的编号
访问地址
类型 *指针变量名
int *p,i;
p=&i;
指针p存放的是i的地址,通过*p来访问i的内容
注意:
指针变量只能接收相同类型变量的地址
指针变量只能在确定初值后才能使用
指针变量作为函数参数
定义:
将一个变量的地址传送到另一个函数中
指针作函数参数传递的是内存的地址
这样可以方便修改地址里的内容
(相当于传送引用)
指针引用一维数组的元素
注意:
C++规定p+1指向的是数组的下一个元素,而不是下一个字节
注意到*与++的优先级问题,因为*与++的优先级相同,所以没有括号的情况下进行右结合
int a[4] = { 3,8,5,9 };
int* p = a;
cout << *++p << endl;//8
cout << ++ * p << endl;//9
当指针指向数组首地址并传入数组的时候
用数组名作为形参,因为接受的是地址,所以不需要确定具体的元素个数
函数指针变量与指针型函数
具体实现看下图:
int add(int a, int b) { return a + b; }
int main()
{
int k, (*f)(int, int), a = 5, b = 10;
f = add;
cout << f(a, b);//(*f)(a,b)
}
指针数组
指针数组中的每一个元素都是指针变量
const指针
指针常量(指针只能指向一个地址,但内容可以修改)
int n,i;
int *const pn=&n;
*pn=25;//正确
pn=&i;//错误
指向常量的指针变量(指针指向的内容不可以修改,但指向的地址可以修改)
int n,i;
const int *pn=&n;
*pn=25;//错误
pn=&i; //正确
指向常量的常指针 (地址和内容都不可以修改)
int n,i;
const int *const pn=&n;
*pn=25;//错误
pn=&i; //错误
动态内存分配
new运算符
指针变量名=new 类型名(初始化值);
int *p;
p=new int(6);
delete运算符
delete 指针名
delete []指针名//释放连续的存储空间
实例
int main()
{
int* p, n; double s = 0;
cin >> n;
p = new int[n];//分配动态存储空间 首地址为p
if (p ==NULL)
{
cout << "NO" << endl;
exit(1);
}
for (int i=0;i<n;i++)
{
cin >> p[i];//向已分配的空间输入数据
}
delete[]p;//释放动态分配的存储空间
}
结构体
struct enum
{
int a;
char b;
float c;
};//这里必须加分号
struct
{
int a;
}s1;//可以不加类型名直接定义变量
struct student
{
int num;
char a[20];
char s;
}stu1,stu2 = { 111,"lihong",'F' };
//除了初始化以外不可以整体赋值
访问成员的两个途径
stu1.num=111;
student *p=&stu2;
p->num;(*p).num;
在初始化的时候,可以部分初始化,其余置为0;
struct enum
{
int a;
char b;
float c;
};
student *p;
p=&stu;
//指针引用方法:
(*P).num //.优先级最高,所以指针需要加括号
p->num //->优先级最高
链表
正向链表
代码段( 如何创建一个链表)
struct node
{
int data; //数据域
node* next;//指针域
};
node* tail_create()
{
node* head, * n, * tail;
int a;
head = NULL;
cout << "建立单向链表,输入数据";
for (cin >> a; a != -1; cin >> a)
{
n = new node;//创建一个新结点
n->data = a;//将数据投入
if (!head)tail = head = n;//如果当前链表为空,则将当前结点同时赋为头和尾
else tail->next = n, tail = n;//如果非空,则将上一个结点的指针域指向当前结点(即尾部)
}
if (head)tail->next = NULL;//如果链表非空,则将尾部结点的指针域置0,表示该结点已经是末尾了
return head;
}
代码段(如何输出当前链表)
void print(const node* head)
{
while (head)
{
cout << head->data << '\t';//输出当前结点内容
head = head->next;//将指针指向下一个结点
}
cout << endl;
}
代码段(如何删除目标结点)
node* Delete(node* head, int num)
{
node* p1, * p2;
if (head == NULL)//空链表
{
return NULL;
}
if (head->data == num)
{
p1 = head;
head = head->next;
delete p1;
}
else
{
p1 = head; p2 = p1->next;
while (p2->data != num && p2->next != NULL)
{
p1 = p2;
p2 = p2->next;
}//每次进行查找删除的位置
if (p2->data == num)
{
p1->next = p2->next;
delete p2;
}
else cout << "无目标结点" << endl;
}
return head;
}
通过如下代码表示,delete运算符可以用来析构基本数据类型(但必须是析构地址)
int main()
{
int a = 2;
int* p = &a;
delete p;//delete &a
cout << a << endl;
}
代码段(如何插入目标结点)
struct node
{
int data; //数据域
node* next;//指针域
};
node* insert(node* head, node* n)
{
node* p1, * p2;
if (head == NULL)
{
head = n; n->next = NULL; return head;
}//当链表为空时,将新结点直接插入
if (head->data >= n->data)
{
n->next = head; head = n; return head;
}//如果新结点应该放在首结点之前
p2 = p1 = head;
while(p2->next&&p2->data<n->data)
{
p1 = p2; p2 = p2->next;
}//寻找满足条件的结点位置
if (p2->data < n->data)//新结点放在尾结点之后
{
p2->next = n; n->next = NULL;
}
else//结点放在p1和p2之间
{
n->next = p2; p1->next = n;
}
return head;
}
反向列表
struct node
{
int data; //数据域
node* next;//指针域
};
node* head_create()
{
node* head, * n;
int a;
head = NULL;
for (cin >> a; a != -1; cin >> a)
{
n = new node;//每次执行一次循环自动析构当前new 指针
n->data = a;
if (!head)head = n, n->next = NULL;//空链表
else n->next = head, head = n;//非空链表
}
return head;
}
共用体
定义
允许不同的数据类型使用同一存储区域即为共用体
union data1
{
char ch[4];
int a;
double f;
};
data1 d1;//这其中有8个字节共享(寻找最大的成员变量)
data1 d2;
注意事项
- 共用体的空间在某一时刻只有一个成员在起作用
- 共用体变量不能在定义的时候赋初值
- 共用体变量不能作为函数的参数或者返回值,但可以使用指针变量指向共用体
- 共用体和结构体可以互相作为成员
- 变量的内容时最后一次赋值的成员内容
代码段
union data1
{
int i;
char s[4];
char ch;
};
int main()
{
data1 d;
d.i = 0x424344;
cout << d.i << endl;//4342596
cout << d.s << endl;//DCB(默认读取ascii从后面往前读)
cout << sizeof(data1) << endl;//4
cout << d.ch << endl;//D
return 0;
}
枚举
定义
如果变量只有几种可能的值
则可以定义为枚举类型,并将他们一一列举出来
enum weekday{sun,mon,tue,wed,thu,fri,sat};
默认枚举的元素值为0,1,2,3,4,5,6
如果给枚举变量赋序号值必须强制类型转换
day=(weekday)1;
Typedef声明类型
typedef 已定义的类型 新的类型名
typedef float REAL
类
主要概念
属性
对象
类的特性
- 封装
- 继承
- 多态
定义类的要点
- 类中任何数据成员不得使用extern,auto,register限定其存储类型
- 定义类的时候,不为类分配存储空间
构造函数和析构函数
使用参数化列表构造函数
TRI(int x,int y,int z) a(x),b(y),c(z){}
构造函数与析构函数的调用顺序相反
如果构造函数里有自身类的引用
class a
{
int b;
public:
a() { b = 1; cout << "调用无参数" << endl; }
a(a& c) { b = c.b; cout <<"调用有引用参数" << endl; }
~a()
{
cout << "~" << endl;
}
};
void show(a c)
{
cout << "调用show函数" << endl;
}
int main()
{
a obj;
show(obj);
}
//调用无参数
//调用有引用参数
//调用show函数
//~
//~
即使没有引用参数的构造函数,进行类之间的直接赋值也可以通过默认构造函数完成
不同类型对象的调用规则
继承与派生代码实例
class a
{
int n;
public:
a(int m) { n = m; }
};
class b :public a
{
int m;
public:
b(int b) :a(b) { m = b; }
void show();
};
void b::show()
{
printf("%d", m);
}
int main()
{
b bi(1);
}
三种继承方式
派生类的构造函数与析构函数调用问题
证明了总是先调用基类的构造函数再是进行当前派生类构造函数的调用(析构顺序相反)
class a
{
int b;
public:
a() { b = 1; cout << "调用无参数" << endl; }
~a()
{
cout << "~" << endl;
}
int show()
{
return b;
}
};
class b :public a
{
public:
b() { cout << 'b' << endl; }
~b()
{
cout << "~B" << endl;
}
};
void fun(a A)
{
cout << A.show()<< endl;
}
int main()
{
b obj;
}
//调用无参数
b
~B
~