算法
算法描述
伪代码:自然语言 + 程序语言 + 数学语言(只要能描述清除自己的算法思维即可)
流程图
算法设计与实现
构造算法解决问题
按照自顶向下、逐步求精的方式进行
程序语言实现
递归算法
理论上,任何递归都可以使用循环方法解决
函数调用栈框架
汉诺塔问题
问题分析
是否存在某种简单情形,问题很容易解决
只有一个圆盘时最简单
是否可将问题分解成性质相同单规模较小的问题,且新问题的解答对原始问题有关建意义
对于n > 1,考虑n-1个圆盘,如果能将n-1个圆盘移动到某塔座上,则可以移动第n个圆盘
策略
首先将n-1个圆盘移动到Y上,然后将第n个移动到Z上,最后再将n-1个圆盘从Y移动到Z上
伪代码
void MoveHanoi(unsigned int n,HANOI from,HANOI tmp,HANOI to)
{
if(n==1)
将一个圆盘从from移动到to;
else
{
将n-1个圆盘从from以to为中转移动到tmp;
将圆盘n从from移动到to;
将n-1个圆盘从tmp以from为中转移动到to;
}
}
程序代码
typedef enum{X=1,Y,Z}HANOI;
void MovePlate(unsigned int n,HANOI from,HANOI to)
{
cout << n << ": from " << from << " to " << to << endl;
}
void MoveHanoi(unsigned int n,HANOI from,HANOI tmp,HANOI to)
{
if(1 == n)
MovePlate(n,from,to);
else
{
MoveHanoi(n-1,from,to,tmp);
MovePlate(n,from,to);
MoveHanoi(n-1,tmp,from,to);
}
}
int main()
{
MoveHanoi(3,X,Y,Z);
return 0;
}
/*运行结果:
1: from 1 to 3
2: from 1 to 2
1: from 3 to 2
3: from 1 to 3
1: from 2 to 1
2: from 2 to 3
1: from 1 to 3
*/
递归大多数情况以 if 开头
计算复杂度
忽略所有对变化趋势影响较小的项
忽略所有与问题无关的常数
O(1) – O(logN) – O(sqrt(N) – O(N) – O(N*logN) – O(N^2) …
程序组织与开发方法
典型软件开发流程
需求分析
方案设计
设计程序框架
编程实现
系统测试
复合数据类型
字符
字符存储时实际存储对应的ASCII值
0对应ASCII码48(0x30)
回车与换行:win:\r\n Linux:\n Mac:\r
数组
结构体
不同类型数据对象
指针与引用
指针使用场景
函数通信
函数传参和输出
构造复杂数据结构
动态分配内存和管理
执行特定程序代码
函数指针
常量指针与指针常量
常量指针
指向常量的指针,
不能通过指针修改目标数据对象的值
但可以改变指针值,使其指向其他地方(指向关系可变)
函数入参
int n = 10;
const int *p = &n;
int const *p = &n;
void test(const int *p);
//*p不能变 p可变
指针常量
指针指向的位置不可变化(指向关系不可变)
可以改变指针指向的目标数据值
int n = 10;
int * const p = &n;//p是常量 定义时必须初始化
//*p可变 p不可变
常量指针常量
const int n = 10;
const int * const p = &n;
int const * const p = &n;//const左结合性来记忆
指针与函数返回值
不能返回函数内部定义的局部变量地址
指针与复合数据类型
指针与数组
数组基地址:&a 或 &a[0] 或 a (一维数组)
指针运算
指针与数组的可互换性
数组名为常数,不能在数组格式上进行指针运算:int a[0]; a++;
如何区分数组指针与指针数组:[]优先级高于*,所以没有(),取[]优先级最高即为数组,有()则取*优先级最高为指针
main()
{
int a[5]={1,2,3,4,5};
// int *ptr=(int *)(a+1);
// printf("%d,%d\n",*(a+1),*(ptr-1));//2,1
// int *ptr=(int *)(&a[0]+1);
// printf("%d,%d\n",*(a+1),*(ptr-1));//2,1
int *ptr=(int *)(&a+1);
printf("%d,%d\n",*(a+1),*(ptr-1));//2,5
}
即 a+1 = &a[0] +1 != &a +1 ,需要强化记忆
指针与结构体
字符串
三种理解角度
字符指针
*s(编译器自动添加\0)
char *s; s="hello";//字符串文字首先分配空间,然后将基地址赋值给s
字符数组
多个字符数组连续存储时无法区分存储空间(如果没有字符串结束标志\0)
char s[6]; s="hello";//❌不能直接整体复制,初始化赋值才可以
抽象的字符串整体
string
char str[] = "hello";
cout << sizeof(str) << " " << strlen(str) << endl;// 6 5
动态存储管理
静态内存分配 - 全局变量和静态局部变量
自动内存分配 - 局部变量
动态内存分配 - 匿名数据对象(指针指向的目标数据对象)
确保程序中只有唯一一个指针拥有目标数据对象!
引用
引用类型的变量不占用单独的存储空间
另一个数据对象的别名,与其共享存储空间
引用类型必须在定义时初始化
引用的最大意义:
引用传递 直接修改实际参数值!
引用作为函数返回值,不生成副本
常量(const)引用不能改变目标对象
基于范围的for循环 (c++11)
#include <iostream>
int main()
{
int array[3] = {1,2,3};
for(int & e : array)//for(p = array; p < array + sizeof(array)/sizeof(int); ++p)
{
e += 2;
std::cout << e << std::endl;
}
return 0;
}
智能指针(c++11)
unique_ptr :不允许多个指针共享资源,可以用标准库中的move函数转移指针
shared_ptr :多个指针共享资源
weak_ptr :可复制shared_ptr,但其构造或者释放对资源不产生影响
链表与程序抽象
数据抽象
程序抽象
数据封装&信息隐藏
链表
面向对象编程
面向对象的基本特点
抽象
数据抽象:描述某类对象的属性或状态
代码抽象:描述某类对象的共有的行为特征或具有的功能
封装
增强安全性和简化编程
继承
扩展新类
多态
重载函数和虚函数
protected explicit protected
构造函数、析构函数无返回值!析构函数无形参!
默认构造函数
参数列表为空,不为数据成员设置初始值;
如果类内定义了成员的初始值,则使用内类定义的初始值;
如果没有定义类内的初始值,则以默认方式初始化;
基本类型的数据默认初始化的值是不确定的。
构造函数和析构函数的调用顺序
使用构造函数创建对象的顺序与使用析构函数释放对象的顺序相反
创建派生类的对象,基类的构造函数函数优先被调用(也优先于派生类里的成员类)
如果类里面有成员类,成员类的构造函数优先被调用
基类构造函数如果有多个基类则构造函数的调用顺序是某类在类派生表中出现的顺序而不是它们在成员初始化表中的顺序
成员类对象构造函数如果有多个成员类对象则构造函数的调用顺序是对象在类中被声明的顺序而不是它们出现在成员初始化表中的顺序
=default
如果程序中已定义构造函数,默认情况下编译器就不再隐含生成默认构造函数。如果此时依然希望编译器隐含生成默认构造函数,可以使用=default
c++11 支持类内变量初始化
枚举类
enum class 枚举类型名: 底层类型 {枚举值列表};
强作用域,其作用域限制在枚举类中
转换限制,枚举类对象不可以与整型隐式地互相转换
可以指定底层类型
数据共享和保护
标识符作用域
函数原型作用域
局部作用域
类作用域
对于两个嵌套的作用域,如果在内层作用域内声明了与外层作用域中同名的标识符,则外层作用域的标识符在内层不可见
对象的生存期
静态生存期
这种生存期与程序的运行期相同
在文件作用域中声明的对象具有这种生存
在函数内部声明静态生存期对象,要冠以关键字static
动态生存期
块作用域中声明的,没有用static修饰的对象是动态生存期的对象(习惯称局部生存期对象)
开始于程序执行到声明点时,结束于命名该标识符的作用域结束处
类的静态成员
类的友元
友元函数
友元函数是在类声明中由关键字friend修饰说明的非成员函数,在它的函数体中能够通过对象名访问 private 和protected成员
友元类
若一个类为另一个类的友元,则此类的所有成员都能访问对方类的私有成员
声明语法:将友元类名在另一个类中使用friend修饰说明
class A
{
friend class B;
private:
int a = 10;
};
class B
{
A i;
public:
void test()
{
cout << i.a;
}
};
类的友元关系是单向的
共享数据的保护
对于既需要共享、又需要防止改变的数据应该声明为常类型
对于不改变对象状态的成员函数应该声明为常函数
常对象:必须进行初始化,不能被更新; const 类名 对象名
class A
{
private:
int x,y;
public:
A(int i,int j){x=i,y=j;}
}
A const a(3,4);
通过常对象只能调用它的常成员函数 !!!
常成员:const 修饰的类成员;常数据成员和常函数成员
常成员函数:类型 函数名(参数表) const;
const是函数类型的一个组成部分,因此在实现部分也要带const关键字
const关键字可以被用于参与对重载函数的区分
常引用:const 类型 &引用名
常数组:类型 const 数组名[大小]
常指针
#include <iostream>
using namespace std;
class A
{
public:
int a;
public:
A(int i):a(i)
{
}
void test()
{
cout << "this is nonconst" << endl;
}
void test() const
{
cout << "this is const" << endl;
}
};
int main()
{
A a(10);
a.test();
a.a = 100;
A const aa(10);//如果有数据成员 必须初始化
aa.test();
//aa.a = 100;//过不了,常对象必须进行初始化,不能被更新
return 0;
}
下列关于常成员的说法不正确的是哪一个(C)
A. 常数据成员必须进行初始化,并且不能被更新
B. 常数据成员可以在定义时直接初始化(C++11)
C. 常成员函数不可以被非常对象调用 (常对象只能调用常成员函数,但非常对象都可以调用)
D. 常数据成员通过构造函数的成员初始列表进行初始化