目录
一、关于蛇形矩阵
1.定义:
矩阵的输入输出一般使用二维数组,由于其列数和行数由控制台输入,所以要定义的是一个动态二维数组,如下所示:
int rows , cols;
cin >> rows >> cols;
//定义:
int **arr = new int *[rows];
for(int i = 0 ; i < rows ; i++)
{
arr[i] = new int [cols];
}
2.输入:
有了定义,下一步就是要考虑如何输入,通过观察蛇形矩阵的特点,考虑边界条件和方向切换的情况,可以通过以下的方式进行输入:
int rowStart = 0 , rowEnd = rows - 1;
int colStart = 0 , colEnd = cols - 1;
int num = 1;
while (rowStart <= rowEnd && colStart <= colEnd)
{
//行(从左到右输入):从 colStart 列到 colEnd 列
for (int i = colStart; i <= colEnd; i++)
{
arr[rowStart][i] = num;
num = num + 1;
}
rowStart = rowStart + 1;//(输入完一行,使得开始行数加一)
//列(从上往下输入):从 rowStart 行到 rowEnd 列
for (int i = rowStart; i <= rowEnd; i++)
{
arr[i][colEnd] = num;
num = num + 1;
}
colEnd = colEnd - 1;//(输入完一行,使得终止列数减一)
//行(从右到左输入):从 colEnd 列到 colStart 列
for (int i = colEnd; i >= colStart; i--)
{
arr[rowEnd][i] = num;
num = num + 1;
}
rowEnd = rowEnd - 1;//(输入完一行,使得终止行数减一)
//列(从下往上输入):从 rowEnd 行到 rowStart 列
for (int i = rowEnd; i >= rowStart; i--)
{
arr[i][colStart] = num;
num = num + 1;
}
colStart = colStart + 1;//(输入完一行,使得开始列数加一)
}
3.输出与释放:
接下来就是很常规的输出和内存的释放:
//输出
for(int i = 0 ; i < rows ; i++)
{
for(int j = 0 ; j < cols ; j++)
{
cout << setw(3) << arr[i][j];//setw()用于控制输出格式
}
cout << endl;
}
//释放内存:
for(int i = 0 ; i < rows ; i++)
{
delete [] arr[i];
}
delete [] arr;
arr = NULL;
二、有序单循环链表的插入
关于链表,本次学习主要通过链表基础知识详解(非常详细简单易懂)-CSDN博客一文,下面是一些简单的感悟和代码书写的思路:
1.链表结构的构建:
(链表由一个个节点构成,每个节点一般采用结构体的形式组织)
链表节点分为两个域
数据域:存放各种实际的数据
指针域:存放下一节点的首地址
typedef struct Node
{
//数据域
int data;
//指针域
struct Node* next;
}node;
2.输入:
//node **p_head:使用指向指针的指针
//原因:需要修改指向链表头部的指针。C++ 中的函数参数传递是值传递,
// 即函数内对参数的修改不会影响到外部的实际参数。
// 因此,如果想要在函数内修改外部的指针(比如修改链表头指针),就需要传入指针的指针,
// 这样在函数内修改指针的指向就可以影响到外部的实际指针。
//node *p_new:插入的新节点
//原因:由于只需要修改新节点本身的数据域和指针域,并不需要修改该节点在外部的指向,
// 所以直接传入指针即可
void link_insert_data(node **p_head , node *p_new)
{
//定义两个指针pb和pf,用来遍历链表。
node *pb,*pf;
//将pb和pf初始化为指向链表头部的指针
pb = pf = *p_head;
//链表为空链表:
if(*p_head == nullptr)
{
//新节点p_new作为链表的头节点
*p_head = p_new;
//将新节点的next指针置为nullptr,表示新节点是链表中的最后一个节点
p_new->next = nullptr;
return;
}
//链表非空链表:
//查找新节点应该插入的位置
while((p_new->data >= pb->data) && (pb->next !=nullptr) )
{
pf = pb;
pb = pb->next;//继续向后遍历
}
//找到一个节点的num比新来的节点num大,插在pb的前面
if(p_new->data < pb->data)
{
//找到的节点是头节点,插在最前面
if(pb == *p_head)
{
p_new->next = *p_head;
*p_head = p_new;
}
//找到的节点不是头节点,将新节点插入到找到的节点(pf所指)之前。
else
{
pf->next = p_new;
p_new->next = pb;
}
}
//没有找到比新节点数据(p_new->data)大的节点,则新节点应该插在链表的最后
else
{
pb->next = p_new;
p_new->next = nullptr;
}
}
//大概还可以写成:(是一些尝试过程中的产物)
//输入不太一样,但效果应该差不多
void insertInSortedCircularLinkedList(node *head, int value)
{
node *p_new = new Node();
p_new->data = value;
p_new->next = nullptr;
if (head == nullptr)
{
// 空链表情况
head = p_new;
p_new->next = p_new; // 让新节点指向自身,构成循环
}
else if (value <= head->data)
{
// 新节点的值比头节点小或等于头节点的值,插入到头部
p_new->next = head;
node *last = head;
while (last->next != head)
{
last = last->next; // 找到最后一个节点
}
last->next = p_new; // 更新尾节点的 next 指针
head = p_new; // 更新头节点
}
else
{
// 中间或尾部插入
node *current = head;
while (current->next != head && current->next->data < value)
{
current = current->next;
}
p_new->next = current->next;
current->next = p_new;
}
}
或者:
node* insertInSortedCircularLinkedList(node *head, int value)
{
node* newNode = new Node();
//使用 new 运算符在堆内存中动态分配了一个 Node 类型的对象,并将返回的地址赋给指针 newNode
newNode->data = value;
if (head == nullptr)
{
newNode->next = newNode;
return newNode;
}
node* pb = head ;
node* pf = nullptr;
do
{
pf = pb;
pb = pb->next;
}
while (pb != head && newNode->next >= pb->data);
if (pb == head)
{
newNode->next = head;
pf->next = newNode;
return newNode;
}
pf->next = newNode;
newNode->next = pb;
return head;
}
3.输出:
void link_print(node *head)
{
if (head == nullptr)
{
cout << "链表为空" << endl;
return;
}
node *p_mov;
//定义新的指针保存链表的首地址,防止使用head改变原本链表
p_mov = head;
//当指针回到头结点时,表示已经遍历完整个循环链表
//当指针保存最后一个结点的指针域为nullptr时,循环结束
while(p_mov != nullptr)
{
//先打印当前指针保存结点的指针域
cout << p_mov->data << " ";
//指针后移,保存下一个结点的地址
p_mov = p_mov->next;
}
cout << endl;
}
//也可以写成do-while 循环的形式,效果一样
do
{
//先打印当前指针保存结点的指针域
cout << p_mov -> data << " ";
//指针后移,保存下一个结点的地址
p_mov = p_mov -> next;
} while(p_mov != head);
4.释放:
//加入之后会使退出变得非常卡顿,在此留疑
void freeCircularLinkedList(node* head)
{
if (head == nullptr)
{
return;
}
//定义一个指针变量保存头结点的地址
node* current = head;
node* temp;
do{
temp = current;
current = current -> next;
delete temp;
} while (current != head);
}
5.目录:
void menu(node*& head)
{
cout << "****** Data Structure ******" << endl;
cout << "1——有序插入单列表" << endl;
cout << "2——查看单列表" << endl;
cout << " 退出,请输入0" << endl;
int sel;
while(cin >> sel)
{
if(sel == 0)
{
//freeCircularLinkedList(head);
cout << endl << "期待您的下次使用!" ;
break;
}
switch(sel)
{
case 1:
{
node* p_new = new Node();//申请一个新节点
cout << "请输入要插入的值:"; //给新节点赋值
cin >> p_new->data;
p_new->next = nullptr;
link_insert_data(&head,p_new);
break;
}
case 2:
{
link_print(head);
break;
}
default:
{
cout << endl;
cout << "您输入数字有误,请重新输入:";
break;
}
}
}
}
6.主函数:
int main()
{
node* head = nullptr;
menu(head);
return 0;
}
*一些学到的新知识:
1.delete和free的区别:
delete 和 free 是释放内存的两种方式,主要用于释放动态分配的内存,但在 C++ 和 C 语言中有一些区别:
delete:
delete 是 C++ 中的关键字,用于释放使用 new 运算符动态分配的单个对象或对象数组的内存。
delete 会调用对象的析构函数,然后释放内存空间。
使用 delete 释放指向对象的指针后,指针会自动设置为 nullptr。
不能用 delete 来释放使用 malloc() 或 calloc() 分配的内存。free:
free 是 C 语言中的函数,用于释放动态分配的内存。
free 只是简单地释放内存,不会调用任何析构函数。
调用 free 后,指针并不会被自动置空,可能会导致野指针问题。
不能用 free 来释放使用 new 运算符分配的内存。
总的来说,如果在 C++ 中动态分配了内存,应该使用 delete 进行释放;在 C 语言中动态分配内存则应使用 free 进行释放。在 C++ 中,最好避免混合使用 new/delete 和 malloc/free,以避免因为不同的释放方式导致的问题。
2. nullptr和NULL的区别:
在C++中,
nullptr
是 C++11 中引入的空指针常量,用于表示空指针。而NULL
在较早的 C++ 标准中就存在,通常被定义为宏或者整数 0,也用于表示空指针。区别:
nullptr
是 C++11 引入的空指针常量,具有明确的空指针类型,可以隐式转换为任意指针类型,且不会与整数 0 混淆。NULL
在较早的 C++标准中就存在,通常被定义为宏或者整数 0,可能会与整数 0 混淆,不具有明确的空指针类型。*最好使用
nullptr
来表示空指针,因为它更加明确和安全,避免了与整数 0 的混淆。
三、类设计题目
1.基类和继承类
在写此题目之前,需要提前了解什么是基类和继承类,本次学习通过Th3.11:基类与派生类关系之详细再讨论_派生类是基类的子集-CSDN博客一文了解,在此仅简述其运用方法:
class Dad {};
class Son : public Dad {};//C++中给出了final关键字,可以防止误用了不想当基类的类作为基类
类型1
class Dad final {//让不想做基类的Dad类声明为final的!
public:
/*...*/
};
class Son : public Dad {//错误!Dad不能用作基类
public:
/*...*/
};
类型2
class Dad {
public:
/*...*/
};
class Son final : public Dad {//让不想做基类的Son类声明为final的!
public:
/*...*/
};
class GrandSon:public Son{//错误!Son不能用作基类
public:
/*...*/
}
多余不再赘述
2. 基类的建设:
#ifndef ACCOUNT_H
#define ACCOUNT_H
using namespace std;
class Account
{
private:
double balance;
public:
Account()
{
}
Account(double Balance)
{
if (Balance >= 0)
{
balance = Balance;
}
else
{
balance = 0;
cout << "\t\t该初始化余额是一个无效的值,已将账户余额定义为0" << endl;
}
}
~Account()
{
}
void credit(double amount)
{
balance = balance + amount;
}
void debit(double amount)
{
if (amount > balance)
{
cout << endl << "\t\tDebit amount exceeded account Balance." << endl;
}
else
{
balance = balance - amount;
}
}
double getBalance() const
{
return balance;
}
};
#endif // ACCOUNT_H
3.派生类的建设:
#ifndef SAVINGSACCOUNT_H
#define SAVINGSACCOUNT_H
#include "Account.h"
using namespace std;
class SavingsAccount : public Account
{
private:
double Rate;
int Year;
public:
SavingsAccount()
{
}
SavingsAccount(double Balance , double rate , int year) :
Account(Balance) , Rate(rate) , Year(year){}
~SavingsAccount()
{
}
double calculate()
{
double total = getBalance();
for (int i = 1; i <= Year; i++)
{
double interest = total * Rate * 0.01;
total = total + interest;
}
return total;
}
};
#endif // SAVINGSACCOUNT_H
4.主函数的建设:
(利用C++中的控制台界面来实现可视化菜单操作(大概可以算是某种程度上的可视化))
#include <iostream>
#include "SavingsAccount.h"
using namespace std;
void menu(SavingsAccount &account)
{
system("cls");
cout << "\t\t************************************" << endl;
cout << "\t\t当前余额为:" << account.getBalance() << endl;
cout << "\t\t************************************" << endl;
cout << "\t\t****** 1.存款 ******" << endl;
cout << "\t\t****** 2.取款 ******" << endl;
cout << "\t\t****** 3.算存款之后的总余额 ******" << endl;
cout << "\t\t****** 0.退出 ******" << endl;
cout << "\t\t************************************" << endl;
cout << "\t\t请输入您的选择:";
}
int main()
{
double Balance , amount;
cout << "\t\t创建一个账户,并输入初始余额:";
cin >> Balance;
cout << endl;
SavingsAccount account(Balance , 0 , 0);
system("pause");//请按任意键继续
menu(account);
int sel;
while(cin >> sel)
{
cout << endl;
if(sel == 0)
{
cout << "\t\t期待您的下次使用!" ;
exit(0);
//exit(0)函数可以立即终止程序的执行,并返回给操作系统。
//参数0表示正常退出,非零值表示异常退出
}
switch(sel)
{
case 1:
cout << "\t\t请输入存款金额:";
cin >> amount;
account.credit(amount);
cout << endl;
system("pause");//请按任意键继续
menu(account);
break;
case 2:
cout << "\t\t请输入取款金额:";
cin >> amount;
account.debit(amount);
cout << endl;
system("pause");//请按任意键继续
menu(account);
break;
case 3:
double rate;
int year;
cout << "\t\t请输入存款年利率(单位:%):";
cin >> rate;
cout << "\t\t请输入存款年份:";
cin >> year;
cout << endl;
account = SavingsAccount(account.getBalance() , rate , year);
cout << "\t\t账户经过" << year <<
"年(年利率为:" << rate << ")后,账户的总余额为:" <<
account.calculate() << endl;
cout << endl;
system("pause");//请按任意键继续
menu(account);
break;
default:
cout << "\t\t您输入数字有误,请重新输入:";
break;
}
}
return 0;
}
*本来想尝试一下改背景颜色,但是不太好看就没改,在此附上修改方式,参考c++可视化操作(一)——控制台点击型菜单(windows.h)-CSDN博客一文
#include <Windows.h>
//背景白色
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(handle, BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY);
// 将文本颜色设置为淡蓝色HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(handle, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
四、结语
本次学习到此结束啦,虽然可能程序上还有很多的不足(欢迎各位指正),还是感谢各位可以看我写到这里。