C++面向对象高级编程(侯捷)笔记2
侯捷C++面向对象高级编程
本文是学习笔记,仅供个人学习使用,如有侵权,请联系删除。
如果你对C++面向对象的组合、继承和委托不了解,对什么是拷贝构造、什么是拷贝赋值和析构不清楚,对类设计中的Adapter、pImpl、Template method、Observer、Composite、Prototype等设计模式认识不清,想知道在面向对象中如何引入设计模式,那么这门课就是你需要的。
学习地址:
Youtube: C++面向对象高级编程(侯捷)
B站: 侯捷C++之C++面向对象高级编程(上)
文章目录
- 侯捷C++面向对象高级编程
- 7 三大函数:拷贝构造、拷贝赋值、析构
- 8 堆 栈和内存管理
- 9 复习String类的实现过程
- 10 扩展补充:类模板,函数模板及其他
- 11 组合与继承
- Composition复合,表示has-a
- Delegation委托. Composition by reference
- Inheritance(继承),表示is-a
- 12 虚函数和多态
- 13 委托相关设计
7 三大函数:拷贝构造、拷贝赋值、析构
Class with pointer member(s)
String class的框架
// string.h
#ifndef __MYSTRING__
#define __MYSTRING__// 1
class String
{...
};
// 2
String::function(...)...
Global-function(...)...
#endif// string-test.cpp
int main()
{String s1();String s2("hello");String s3(s1);cout << s3 << endl;s3 = s2;cout << s3 << endl;
}
Big Three 三个特殊函数:拷贝构造,拷贝赋值,析构函数
class String
{
public:String(const char* cstr = 0);String(const String& str); // 拷贝构造,接收的是自己一样的类型StringString& operator = (const String& str); // 拷贝赋值~String(); // 析构函数char* get_c_str() const {return m_data;}
private:char* m_data;
};
构造函数和析构函数
class里面有指针,多半要动态分配,对象要死亡之前,会调用析构函数,在析构函数里面释放掉动态分配的内存。
inline
String::String(const char* cstr = 0)
{if(cstr) {m_data = new char[strlen(cstr) + 1]; // +1是因为最后还有一个结束符号\0strcpy(m_data, cstr);}else { // 未指定初值,设为空字符串m_data = new char[1];*m_data = '\0';}
}inline
String::~String()
{delete[] m_data;
}// 使用
{String s1();String s2("hello");String* p = new String("hello");delete p;
}
Class with pointer member(s)必须要拷贝构造和拷贝赋值
String a("hello");
String b("hello");b = a;
使用默认拷贝构造或者默认拷贝赋值,会造成b的m_data指针指向a的空间,造成b原本指向的内存空间泄露(没有指针指向它),这个叫浅拷贝(只是指针指过去,不创建新的空间)
深拷贝:创建一份新的内存空间并拷贝过去
inline
String::String(const String& str)
{m_data = new char[strlen(str.m_data) + 1];strcpy(m_data, str.m_data);
}// 使用
{String s1("hello");String s2(s1);String s2 = s1;
}
拷贝赋值操作 copy assignment operator
inline
String& String::operator=(const String& str)
{if(this == &str) // 检测自我赋值return *this;delete[] m_data; // 1. 先释放掉自己的部分的空间m_data = new char[strlen(str.m_data) + 1]; // 2.重新分配跟str一样大的空间strcpy(m_data, str.m_data); // 3.将str的内容拷贝到新分配的空间return *this;
}// 使用
{String s1("hello");String s2(s1);String s2 = s1;
}
一定要在拷贝赋值操作里面检查自我赋值
如果是 s1 = s1,若没有检查自我赋值的情况下,首先会delete掉自己,然后重新分配右边的(自己)同样大小的空间就无从谈起了,会产生不确定的行为。
output函数
#include<iostream.h>
ostream& operator <<(ostream& os, const String& str)
{os << str.get_c_str();return os;
}// 使用
{String s1("hello");cout << s1;
}
8 堆 栈和内存管理
所谓stack,所谓heap
Stack:是存在于某作用域的一块内存空间。例如当调用函数时,函数本身即会形成一个stack用来放置它所接收的参数,以及返回地址。
在函数体内声明的任何变量,其所使用的内存块都取自stack。
Heap:是指由操作系统提供的一块global内存空间,程序可变动分配从其中获得若干区块。
class Complex {...};
...
{Complex c1(1, 2); // c1占用的空间来自stackComplex* p = new Complex(3); // 从堆动态分配而来
}
stack objects的生命期
c1就是所谓的stack object,其生命在作用域结束之际结束。这种作用域内的object,又被称为auto object,因为它会被自动清理。
static local objects的生命期
class Complex {...};
...
{static Complex c2(1, 2);
}
c2便是所谓的static object,其生命在作用域结束之后依然存在,直到整个程序结束。
global objects的生命期
class Complex {...};
...
Complex c3(1, 2); int main()
{...
}
c3便是所谓的global object,其生命在整个程序结束之后才结束。也可以把它视为一种static object,其作用域是整个程序。
heap objects的生命期
class Complex {...};
...
{Complex* p = new Complex(3); delete p; // delete指针
}
p所指向的便是heap object,其生命在它被deleted之际结束。
class Complex {...};
...
{Complex* p = new Complex(3);
}
以上出现内存泄漏(memory leak),因为当作用域结束,p所指向heap object依然存在,但指针p的生命却结束了,作用域之外再也看不到p(也就没机会delete p)。
new:先分配memory,再调用构造函数
Complex* pc = new Complex(1, 2);// new被编译器转化为:
Complex *pc;
void* mem = operator new(sizeof(Complex)); // 1. 分配内存, operator new是一个函数名,内部调用malloc函数
pc = static_cast<Complex*>(mem); // 2. 转型
pc->Complex::Complex(1, 2); // 3. 构造函数// 上面构造函数隐藏的this指针:
// 这里构造函数是成员函数,所以会有一个隐藏的this指针,谁调用这个函数,谁就是this
Complex::Complex(pc, 1, 2);// 这里pc就是this,只不过它是隐藏的
delete:先调用析构函数,再释放内存
Complex* pc = new Complex(1, 2);
delete pc;// delete被编译器转化为:
Complex::~Complex(pc); // 1. 析构函数
operator delete(pc);// 2. 释放内存, delete内部调用free(pc),operator delete是一个函数名
array new 一定要搭配array delete
String* p = new String[3];
delete[] p; // 唤起3次析构函数,正确的用法//错误的用法,不加[]
String* p = new String[3];
delete p; // 唤起1次析构函数,错误的用法
9 复习String类的实现过程
String类的具体类,构造函数,成员函数等如上面的笔记所示。
10 扩展补充:类模板,函数模板及其他
进一步补充:static
静态成员:当类的成员被定义为静态的时候,无论创建多少个类的对象,静态成员的副本只有一个。静态成员在类的所有对象中是共享的。
静态成员函数:可以把函数和类的对象独立开来,静态成员函数可以在类的对象不存在的情况下被调用。静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。
static function静态函数没有this指针
静态变量的初始化不能放在类的定义中,在类的外部来重新声明来进行初始化。
C++ 类的静态成员
银行的例子:利率为静态变量
class Account {
public:static double m_rate;static void set_rate(const double& x) { m_rate = x;}
};
double Account::m_rate = 8.0;int main(){Account::set_rate(5.0);Account a;a.set_rate(7.0);
}
调用static函数的方式有:
(1)通过object调用
(2)通过class name调用
构造函数可以被放在private区,在设计模式中,单例模式(singleton)便是这样做的
class A
{
public:static A& getInstance();setup() { ... }
private:A();A(const A& rhs);...
}A& A::getInstance()
{static A a; //只有调用的时候,A才会创建,离开这个函数之后,静态变量A依然存在return a;
}
外界调用时
A::getInstance().setup();
没有人使用单例的话,这个单例就不存在;一旦有人用了一次,单例才出现,并且只有一份。
进一步补充 cout
cout是一种ostream,这里对<<进行了各种各样的重载,所以cout可以打印输出各种各样的数据。
进一步补充:class template 类模板
template<typename T>
class complex
{public:complex(T r = 0, T i = 0): re(r), im(i) // 构造函数的初始化列{}complex& operator += (const complex&);T real() const { return re;}T imag() const { return im;}private:T re, im;friend complex& __doapl(complex*, const complex&);
};// 使用模板的例子,指定T的具体类型
{complex<double> c1(2.5, 1.5);complex<int> c2(2, 6);...
}
进一步补充:function template 函数模板
stone r1(2, 3), r2(3, 3), r3;
r3 = min(r1, r2); // 编译器会对function template 进行引数推导template<class T> // 引数推导的结果,T为stone,于是调用stone::operator<
inline
const T& min(const T&, const T&, b)
{return b < a ? b : a;
}class stone
{
public:stone(int w, int h, int we): _w(w), _h(h), _weight(we) {}bool operator<(const stone& rhs) const {return _weight < rhs._weight;}private:int _w, _h, _weight;
};
进一步补充:namespace
命名空间定义了上下文,它可作为附加信息来区分不同库中相同名称的函数、类、变量等。命名空间
namespace name
{...
}
11 组合与继承
Composition复合,表示has-a
template<class T, class Sequence = deque<T> >
class queue {
...
protected:Sequence c; // 底层容器
public:// 以下完全利用c的操作函数完成bool empty() const {return c.empty();}size_type size() const {return c.size();}reference front() {return c.front();}reference back() {return c.back();}// deque是两端可进出,queue是末端进前端出(先进先出)void push(const value_type& x) {c.push_back(x);}void pop() {c.pop_front();}
};
把上面的class Sequence = deque替换进来
class queue {
...
protected:deque<T> c; // 底层容器
public:// 以下完全利用c的操作函数完成bool empty() const {return c.empty();}size_type size() const {return c.size();}reference front() {return c.front();}reference back() {return c.back();}// deque是两端可进出,queue是末端进前端出(先进先出)void push(const value_type& x) {c.push_back(x);}void pop() {c.pop_front();}
};
其中 queue里面has a deque,就是复合关系
这里还有设计模式中的Adapter模式,queue使用deque开放的6个函数,改头换面改装适配一下。这里谁是adapter呢?queue。
UML类图中使用黑色菱形代表复合。
从内存的角度看一下composition
如下图,Itr里面有4个指针,大小共为16B;deque里面有2个Itr、1个指针、1个unsigned int,每个Itr的大小为16B,deque大小共为40B;queue里面有deque,故为40B。
Composition复合关系下的构造和析构
构造由内而外
Container的构造函数首先调用Component的默认构造函数,然后才执行自己。这是编译器帮我们做的。
Container::Container(...): Component() {...};
析构由外而内
Container的析构函数首先执行自己,然后才调用Component的析构函数。
Container::~Container(...) {... ~Component() };
Delegation委托. Composition by reference
还是Container“有“ component,只是有一个指针指向另一个component,比如下面的String类中rep指针,指向StringRep类。
UML类图中用白色的菱形表示委托。
// file String.hpp
class StringRep;
class String {
public:String();String(const char* s);String(const String& s);String& operator=(const String& s);~String();
private:StringRep* rep; // pimpl
};
// file String.cpp
#include"String.hpp"
namespace {
class StringRep{
friend class String;StringRep(const char* s);~StringRep();int count;char* rep;
};
}String::String() {...}
...
pimpl:pointer to Implementation,另一个名字叫做handle/body
外界看到的是左边的接口,而右边的实现通过一个指针指向,外界看不到。
Inheritance(继承),表示is-a
子类继承父类的数据,成员函数
struct _List_node_base
{_List_node_base* _M_next;_List_node_base* _M_prev;
};template<typename _Tp>
struct _List_node: public _List_node_base // 继承的语法
{_Tp _M_data;
};
UML类图中使用空心的三角形表示继承,从子类指向父类
继承关系下的构造和析构
父类是Base,Derived是派生类,派生类的对象里面有父类的成分在里面。
构造由内而外
Derived的构造函数首先调用Base的默认构造函数,然后才执行自己。
Derived::Derived(...): Base() {...};
析构由外而内
Derived的析构函数首先执行自己,然后才调用Base的析构函数。
Derived::~Derived(...) {... ~Base()};
base class的析构函数必须是virtual,否则会出现undefined behavior。
12 虚函数和多态
Inheritance with virtual functions 虚函数
non-virtual函数:不希望derived class重新定义
virtual函数:希望derived class重新定义(override),且它已有默认定义。
pure virtual函数:希望derived class一定要重新定义,对它没有默认定义
class Shape{
public:virtual void draw() const = 0; // 纯虚函数, 被子类重新定义virtual void error(const std::string& msg); // 虚函数, 父类有默认的定义int objectID() const;
};
class Rectangle:public Shape{...};
class Ellipse:public Shape{...};
打开文件的例子
这种写法是一个大名鼎鼎的设计模式:Template method
函数的一个动作延缓到子类去实现。应用框架先把固定功能的函数写好,遇到无法决定的函数把它写成虚函数,让应用这个框架的子类去定义它。
13 委托相关设计
委托和继承
Observer设计模式:定义了一种一对多的依赖关系,当一个对象的状态发生改变时,其所有依赖者都会收到通知并自动更新。
Subject:比如excel中的数据
Observer:使用者可以开多个窗口来观察Subject,比如使用柱状图、折线图等不同的窗口来观察excel中的数据。这就是通过派生子类来实现的。
Subject和Observer是指针指向(委托)的关系
class Subject
{int m_value;vector<Observer*> m_views; // 委托,使用指针指向另一个类public:void attach(Observer* obs){m_views.push_back(obs);}void set_val(int value){m_value = value;notify();}void notify(){for (int i = 0; i < m_view.size(); ++i)m_views[i]->update(this, m_value);}
};class Observer
{
public:virtual void update(Subject* sub, int value) = 0;
};
Composite设计模式
组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。
这里是用Primitive,Composite和Component三个类合作完成的。Composite类想要放不同类的对象,这样让不同的类继承自同一个父类,这样就可以把父类的指针存在Composite里,从而实现把一组相似的对象都存进来。
Prototype设计模式
原型模式
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。
遇到问题:如下图所示,波浪线上面是提前写好的类,但是它要用到未来才出现的类,该怎么办呢?
波浪线下面的类继承上面的类,然后调用父类的addPrototype将自己添加到父类的空间,让父类看到子类。子类调用clone函数生成自己的拷贝。
父类如下:
子类如下:下图中的ctor指的是构造函数
相关文章:

C++面向对象高级编程(侯捷)笔记2
侯捷C面向对象高级编程 本文是学习笔记,仅供个人学习使用,如有侵权,请联系删除。 如果你对C面向对象的组合、继承和委托不了解,对什么是拷贝构造、什么是拷贝赋值和析构不清楚,对类设计中的Adapter、pImpl、Template…...
双曲正弦函数(*) 优化麦克劳林公式
#include<stdio.h> #include<math.h> int main() {double x,eps,i3,y,item;scanf("%lf%lf",&x,&eps);yx;itemx;while(fabs(item)>eps){itemitem*x*x/i/(i-1);i2;yitem;}printf("%.6f\n",y);return 0; }...

无监督关键词提取算法:TF-IDF、TextRank、RAKE、YAKE、 keyBERT
TF-IDF TF-IDF是一种经典的基于统计的方法,TF(Term frequency)是指一个单词在一个文档中出现的次数,通常一个单词在一个文档中出现的次数越多说明该词越重要。IDF(Inverse document frequency)是所有文档数比上出现某单词的个数,通常一个单词…...

web3 : blockscout剖析
Blockscout 是第一个功能齐全的开源区块链浏览器,可供任何以太坊虚拟机 (EVM) 链使用。项目方可以下载并使用Blockscout作为其链的浏览器,用户可以轻松验证交易、余额、区块确认、智能合约和其他记录。 目录 Blockscout可以做什么主要特征blockscoutDocker容器组件Postgres 1…...

【机器学习基础】DBSCAN
🚀个人主页:为梦而生~ 关注我一起学习吧! 💡专栏:机器学习 欢迎订阅!相对完整的机器学习基础教学! ⭐特别提醒:针对机器学习,特别开始专栏:机器学习python实战…...
计算机硬件 4.4键盘与鼠标
第四节 键盘与鼠标 一、认识键盘 1.地位:计算机系统最基本的输入设备。 2.外观结构:面板、键帽、底盘、数据线。 3.组成键区:主键区、功能键区、辅助键区和编辑(控制)键区。 二、键盘分类 1.按接口分 ①AT口&…...

Flappy Bird QDN PyTorch博客 - 代码解读
Flappy Bird QDN PyTorch博客 - 代码解读 介绍环境配置项目目录结构QDN算法重要函数解读preprocess(observation)DeepNetWork(nn.Module)BirdDQN类主程序部分 介绍 在本博客中,我们将介绍如何使用QDN(Quantile Dueling Network)算法…...

听GPT 讲Rust源代码--compiler(9)
File: rust/compiler/rustc_trait_selection/src/traits/select/mod.rs 在Rust源代码中,rust/compiler/rustc_trait_selection/src/traits/select/mod.rs文件的作用是实现Rust编译器的trait选择器。 首先,让我们逐个介绍这些struct的作用: Se…...
Go语言中关于go get, go install, go build, go run指令
go get go get 它会执行两个操作 第一个, 是先将远程的代码克隆到Go Path的 src 目录那二个, 是执行go install命令 那如果指定的包可以生成二进制文件那它就会把这个二进制文件保存到这个 Go Path 的bin目录下面这是 go install 命令执行的操作 如果只需要下载包,…...

石头剪刀布游戏 - 华为OD统一考试
OD统一考试 分值: 100分 题解: Java / Python / C++ 题目描述 石头剪刀布游戏有 3 种出拳形状: 石头、剪刀、布。分别用字母 A,B,C 表示游戏规则: 出拳形状之间的胜负规则如下: A>B; B>C; C>A; 左边一个字母,表示相对优势形状。右边一个字母,表示相对劣势形状。…...

【北亚服务器数据恢复】ZFS文件系统服务器ZPOOL下线的数据恢复案例
服务器数据恢复环境: 服务器中有32块硬盘,组建了3组RAIDZ,部分磁盘作为热备盘。zfs文件系统。 服务器故障: 服务器运行中突然崩溃,排除断电、进水、异常操作等外部因素。工作人员将服务器重启后发现无法进入操作系统。…...

C# 反射的终点:Type,MethodInfo,PropertyInfo,ParameterInfo,Summry
文章目录 前言反射是什么?常用类型操作SummryPropertyInfoMethodInfo无参函数运行 有参函数运行,获取paramterInfo 总结 前言 我之前写了一篇Attribute特性的介绍,成功拿到了Attribute的属性,但是如果把Attribute玩的溜,那就要彻…...

2020年认证杯SPSSPRO杯数学建模D题(第一阶段)让电脑桌面飞起来全过程文档及程序
2020年认证杯SPSSPRO杯数学建模 D题 让电脑桌面飞起来 原题再现: 对于一些必须每天使用电脑工作的白领来说,电脑桌面有着非常特殊的意义,通常一些频繁使用或者比较重要的图标会一直保留在桌面上,但是随着时间的推移,…...

谷歌推出创新SynCLR技术:借助AI生成的数据实现高效图像建模,开启自我训练新纪元!
谷歌推出了一种创新性的合成图像框架,这一框架独特之处在于它完全不依赖真实数据。这个框架首先从合成的图像标题开始,然后基于这些标题生成相应的图像。接下来,通过对比学习的技术进行深度学习,从而训练出能够精准识别和理解这些…...

Vue2中使用echarts,并从后端获取数据同步
一、安装echarts npm install echarts -S 二、导入echarts 在script中导入,比如: import * as echarts from "echarts"; 三、查找要用的示例 比如柱状图 四、初始化并挂载 <template><div id"total-orders-chart" s…...
【Redux】自己动手实现redux-thunk
1. 前言 在原始的redux里面,action必须是plain object,且必须是同步。而我们经常使用到定时器,网络请求等异步操作,而redux-thunk就是为了解决异步动作的问题而出现的。 2. redux-thunk中间件实现源码 function createThunkMidd…...

ElasticSearch使用Grafana监控服务状态-Docker版
文章目录 版本信息构建docker-compose.yml参数说明 创建Prometheus配置文件启动验证配置Grafana导入监控模板模板说明 参考资料 版本信息 ElasticSearch:7.14.2 elasticsearch_exporter:1.7.0(latest) 下载地址:http…...

VS Code 如何调试Python文件
VS Code中有1,2,3处跟Run and Debug相关的按钮, 1 处:调试和运行就不多说了,Open Configurations就是打开workspace/.vscode下的lauch.json文件,而Add Configuration就是在lauch.json文件中添加当前运行Python文件的Configuratio…...

day06、SQL语言之概述
SQl 语言之概述 6.1 SQL语言概述6.2 SQL语言之DDL定义数据库6.3 SQL语言之DML操纵数据库 6.1 SQL语言概述 6.2 SQL语言之DDL定义数据库 6.3 SQL语言之DML操纵数据库...

3D目标检测(教程+代码)
随着计算机视觉技术的不断发展,3D目标检测成为了一个备受关注的研究领域。与传统的2D目标检测相比,3D目标检测可以在三维空间中对物体进行定位和识别,具有更高的准确性和适用性。本文将介绍3D目标检测的相关概念、方法和代码实现。 一、3D目…...

XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...

有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...

MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.
ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #:…...