当前位置: 首页 > news >正文

做购物平台网站客户体验活动ui界面设计实例100例

做购物平台网站客户体验活动,ui界面设计实例100例,江志文周诗雨,哪家做网站最好你这段内容主要在讨论 函数接口设计时#xff0c;如何表达“失败”或“不满足前提条件”的情况#xff0c;特别是处理除法时除数为零的问题。以下是对关键点的理解总结#xff1a; 1. 函数自然接口#xff08;Natural Interface#xff09; 函数接口定义得很简单#x…你这段内容主要在讨论 函数接口设计时如何表达“失败”或“不满足前提条件”的情况特别是处理除法时除数为零的问题。以下是对关键点的理解总结 1. 函数自然接口Natural Interface 函数接口定义得很简单 int integral_div(int num, int denom);表示计算并返回 num / denom 的整数商忽略余数。 理想状态下调用者传入两个参数就得到结果。 2. 问题denom 0怎么办 除数为零是非法操作会导致程序错误。这是一个边界或异常情况需要处理。 3. 用前置条件表达接口约束 // Precondition: denom ! 0 int integral_div(int num, int denom);这里声明调用时必须保证 denom 不为零。责任转移到调用者调用者必须保证满足这个条件。如果调用者不遵守结果未定义可能程序崩溃。 4. 初学者的误区打印错误信息然后继续执行 int integral_div(int num, int denom) {if (!denom)std::cerr Divide by zero std::endl;return num / denom; }这种写法看似提示错误但其实根本没阻止错误发生。除零后程序可能崩溃打印没意义。这表明函数与调用者的交互不清晰。 5. 激进的处理直接终止程序 int integral_div(int num, int denom) {if (!denom) {std::exit(-1); // 或 abort(), terminate() 等}return num / denom; }这是激进的防御性编程。保证函数只在有效输入时运行否则程序立即退出。适合一些关键系统但通常不推荐直接杀死程序。 总结理解 函数接口最好明确约束前置条件让调用者负责提供合法参数。函数内部不应简单“打印错误”然后继续执行那样无效且不安全。激进措施退出程序虽简单粗暴但不能解决所有场景。理想情况是用更优雅的错误处理机制比如异常、返回错误码、或其他错误传播机制来表达失败。 如果你需要我可以帮你写几个示例展示如何更合理地表达函数的失败比如用异常或返回 std::optional。需要吗 这段内容继续讲述了如何在函数接口中表达“失败”或“不满足前置条件”的情况同时给出了多种不同的设计方案及其利弊。下面是逐条理解总结 1. 使用 assert 断言 int integral_div(int num, int denom) {assert(denom divide by zero);return num / denom; }断言检查 denom ! 0不满足时程序终止。这是激进的“防御式”编程保证只有在合法输入下才执行。缺点assert 在 Release 模式通常会被编译器去掉不会检查。需要开发阶段彻底测试保证调用前就不会传入非法值。 2. 客户端代码示例 int main() {int n, d;if (cin n d)cout integral_div(n,d) endl; }如果输入为 0会因为断言失败而程序终止。这种设计保持了函数的自然接口两个参数返回结果调用简单。 3. 改变函数签名添加“成功标志”参数 int integral_div(int num, int denom, bool ok) {if (!denom) {ok false;return -1; // 随意返回一个值}ok true;return num / denom; }通过引用参数 ok 通知调用者是否成功。缺点增加了函数参数调用变得复杂。需要调用者检查 ok否则可能忽略错误。 4. 客户端代码示例 int main() {int n, d;if (cin n d) {bool ok true;int res integral_div(n,d,ok);if (ok)cout res endl;} }代码更复杂调用者必须管理额外的成功标志。容易忘记检查 ok导致潜在错误。 5. 改变函数签名返回成功码输出结果通过引用参数 bool integral_div(int num, int denom, int res) {if (!denom)return false;res num / denom;return true; }这样调用时返回值表示是否成功结果通过引用参数返回。这种方式更符合“布尔值表示成功/失败”的习惯。但函数签名依然和自然接口不同。 6. 客户端代码示例 int main() {int n, d;if (cin n d) {int res;if (integral_div(n,d,res))cout res endl;} }调用代码更清晰逻辑明显先判断是否成功再使用结果。但如果调用者忘记检查返回值依然会有问题。C17 引入的 [[nodiscard]] 属性可以帮助避免忽略返回值。 总结理解 使用断言简单明了保持了自然接口但断言只适合调试阶段不能依赖于生产环境。添加成功标志参数或者返回 bool 并通过引用参数输出结果是比较常用的表达失败的方式但会改变函数的自然接口。这类设计使得调用更安全但调用者负担变重代码更冗长。最理想的是函数接口简洁且失败信息不能被调用者忽略这正是异常机制的优势所在本节未提及但可以理解为后续改进方向。 这段内容主要讲的是如何优雅地表达函数“失败”的情况特别是针对integral_div(num, denom)函数中除数denom可能为0时如何设计函数返回值让调用者能够知道计算是否成功而不是简单地返回一个整数或直接崩溃。 关键点总结 改变返回类型以表达失败 使用 std::pairbool, int 或类似结构std::pairbool,int integral_div(int num, int denom) {if (denom 0) return {false, -1};return {true, num / denom}; }调用时检查bool标志判断是否成功。类似Go语言的返回值模式返回值错误码。使用 C17 的结构化绑定简化调用代码if (int n, d; std::cin n d) {if (auto [ok, res] integral_div(n, d); ok) {std::cout res std::endl;} }使用 std::optionalint 来表示可能失败的结果 返回optionalint若除数为0返回空的optional表示无有效结果。std::optionalint integral_div(int num, int denom) {if (denom 0) return {};return num / denom; }调用时检查是否有值auto res integral_div(n, d); if (res) {std::cout res.value() std::endl; }C17结构化绑定简化调用if (auto res integral_div(n, d); res) {std::cout res.value() std::endl; }使用类似expectedT, E的模式C标准中暂时没有 expected类型包含成功时的结果或错误类型class divide_by_zero{}; expectedint, divide_by_zero integral_div(int num, int denom) {if (denom 0) return divide_by_zero{};return num / denom; }调用时像optional一样检查是否有有效结果同时还能得到具体的错误类型。这比optional更通用可以传递具体错误信息比如std::error_code而不仅仅是“有/无”。 这几种设计思想的核心目的 避免函数内部“直接打印错误”、“终止程序”等不灵活的错误处理手段而是把错误信息通过返回值传递给调用者让调用者决定如何处理。让接口更安全避免非法输入导致程序崩溃。让调用者可以清晰地检测到函数是否成功执行并安全地访问结果。 你理解的关键点 函数自然接口 简单的int integral_div(int num, int denom)但不表达失败。表达失败的改进接口 返回一个能表达成功/失败的类型如pairbool,int、optionalint、expectedint,Error等。客户端代码通过检查返回值状态决定下一步操作。 这部分内容继续探讨了如何优雅地表达函数执行失败的情况但这次是用**抛异常throw**的方式来处理错误比如除以零的情况。 核心内容理解 用异常来表达错误class divide_by_zero {}; int integral_division(int num, int denom) {if (!denom)throw divide_by_zero{};return num / denom; }如果除数为0就抛出一个divide_by_zero类型的异常。这样函数的接口签名看起来完全没变依然是int integral_division(int, int)。但是调用时如果遇到除零程序会跳转到异常处理逻辑。 调用代码例子int main() {int n, d;if (std::cin n d)std::cout integral_division(n, d) std::endl; }这里调用函数看起来和原来完全一样没有像之前那样显式处理错误的返回值。但是程序运行时如果遇到denom 0会抛异常。 疑问没有try/catch块怎么办 代码示例中没有显式的try和catch块异常会向上传递到调用栈外层。如果最终没有捕获异常程序会异常终止通常会调用std::terminate。这是这段内容隐含的思考如果用异常机制调用代码要么包裹try/catch捕获异常要么允许异常继续传播。 这和之前用optional或pair的区别 异常机制的优点 保持函数接口简洁没有改变函数返回值类型。错误处理和正常逻辑分离调用者可以选择在哪里捕获异常。 缺点 需要调用者意识到可能有异常需要写异常处理代码否则程序会崩溃。异常机制的运行时开销较大。异常控制流程不如返回值直观可能导致隐藏的异常路径。 总结 异常是另一种表达“失败”的手段属于“非局部错误处理”机制。它让函数接口保持原样不必返回“可能失败”的类型。调用者需要考虑是否捕获异常。和用optional、expected等“显式返回错误”方法相比异常更隐式但更灵活。 这部分内容讲了异常的“捕获catch”原则和设计示例一个简单的动态数组类 Array主要有两个核心点 1. 异常处理的哲学何时捕获异常 “大多数情况下没有必要捕获异常。” 只有当你能够“做些什么”时才去捕获异常否则直接让它继续传播。捕获了异常不一定马上处理它如果当前层不知道怎么处理可以重新抛出异常rethrow让更上层的代码去处理。这样做的好处是异常处理代码更聚焦错误不会被“无意义的捕获”而吞掉。 2. 示例简单的动态数组 ArrayT 基本结构 elems指向存储元素的动态数组nelems当前元素数量cap数组容量 常用方法 size(), capacity() 返回大小和容量begin(), end() 返回迭代器full() 判断是否已满push_back(const T) 插入元素grow() 扩容方法当满了时调用 grow() 的实现思路 新容量 当前容量的两倍或者初始64新开辟一个数组 p复制旧元素到新数组释放旧数组内存指针和容量更新到新数组和新容量 结合异常处理 这段代码很“天真”没有专门的异常安全设计比如 如果new T[new_cap]失败会抛异常std::bad_allocstd::copy中元素复制可能抛异常如果异常发生在中间可能导致内存泄漏或状态不一致 理想情况下grow()要做到异常安全比如使用std::vector那样的策略或者先分配新内存再复制成功后才切换指针。异常处理设计原则 你不必在所有地方捕获异常除非你能恢复或做补救。否则让异常往上传递由更高层统一处理或终止程序。 总结 异常不是用来随便捕获的只捕获能做实事的。示例的动态数组展示了典型的“可能抛异常的操作”提醒我们设计时要考虑异常安全。这帮助理解如何设计既方便又安全的接口与异常策略。 这段内容讲了用RAII资源获取即初始化来优雅地管理资源和异常安全并介绍了异常处理机制中的特殊情况。 1. 传统的 grow() 实现显式异常处理 void grow() {std::size_t new_cap capacity() ? capacity() * 2 : 64;auto p new T[new_cap];try {std::copy(begin(), end(), p);} catch (...) {delete[] p;throw;}delete[] elems;elems p;cap new_cap; }这里先用 new 分配新内存拷贝旧元素。拷贝过程中可能抛异常所以用 try...catch 捕获异常时释放刚分配的内存避免泄漏再重新抛出异常。代码比较冗长异常处理显得繁琐。 2. 用 RAII 简化资源管理 void grow() {std::size_t new_cap capacity() ? capacity() * 2 : 64;std::unique_ptrT[] p { new T[new_cap] }; // RAII 自动管理内存std::copy(begin(), end(), p[0]);delete[] elems;elems p.release(); // 释放智能指针控制权内存交给 elems 管理cap new_cap; }使用 std::unique_ptrT[] 自动管理新分配的内存。如果 std::copy 抛异常p 会自动析构释放内存不会泄漏。不用手动写异常处理代码更简洁更安全。体现了RAII的强大——资源和异常安全自动管理。 3. 关于异常处理的特殊情况std::terminate 标准说明中提到当异常处理机制遇到以下情况时必须放弃异常处理调用 std::terminate() (1.1) 异常机制在构造异常对象完成后、异常被捕获处理前调用了又抛异常的函数异常传播链中再次抛异常。 (1.2) 找不到合适的异常处理器catch块处理抛出的异常。 在这两种情况下程序会调用 std::terminate()通常意味着程序非正常终止。 这提醒我们异常处理并不是万能的某些情况下异常机制会停止运行程序崩溃。设计异常时要注意避免“异常中再抛异常”的情形。 总结 RAII 是C中实现异常安全和资源管理的利器减少手动异常处理代码。用 std::unique_ptr 管理内存拷贝失败时自动释放防止内存泄漏。异常处理机制有边界和限制异常处理失败时调用 std::terminate()。编写异常安全代码时要考虑这些特殊情况。 这段内容主要讲在C异常处理及错误表达中不同方案的设计权衡和效率问题我帮你总结理解 1. 关于异常处理和 noexcept 及 std::terminate 如果异常传播进入了一个 noexcept 限定的函数该函数声明不允许抛异常且该异常未被捕获处理标准规定 编译器是否会展开unwind调用栈以及展开程度是实现定义的。但在其他情况下没有 noexcept 限定异常传播失败时必须调用 std::terminate()且不能提前停止展开调用栈。 意味着对带有 noexcept 的函数异常处理行为可能因编译器实现不同而不同。 2. 错误处理的总结 仅打印错误信息通常不够好程序会“悬着”用户和开发者都不知道发生了什么。**终止程序或断言assert**是一种合理方案适合一些快速失败、重启的场景也便于开发时捕捉错误。修改函数签名让返回值携带状态如C风格的返回码是经典做法需要调用者主动检查且要保证调用者有良好习惯“纪律”。丰富返回类型用 pair, optional, expected 等封装结果和状态要求调用者显式检查错误但接口依旧简洁。抛异常不改接口错误直接通过异常机制传播调用者用 try/catch 处理。 3. 效率constexpr考量 打印和程序终止terminate, exit, abort无法作为 constexpr 函数使用因为这些操作不是编译时可执行的。使用 assert 可以写成 constexpr因为它在编译时有条件能触发断言constexpr int integral_div(int num, int denom) {return assert(denom ! 0), num / denom; }修改函数签名比如多返回一个 bool ok 状态参数可以写成 constexprconstexpr int integral_div(int num, int denom, bool ok) {if (!denom) {ok false;return -1; // arbitrary}ok true;return num / denom; }返回类型是字面类型如 pairint,bool的版本也能写成 constexprconstexpr std::pairint,bool integral_div(int num, int denom) {return !denom ? std::make_pair(-1, false) : std::make_pair(num/denom, true); }但 optional 和 expected 通常因为有非平凡析构函数不适合做 constexpr。 总结 错误处理方式设计有多种折中于易用性、语义表达、异常安全和效率。constexpr 在错误处理方案中约束了选择简单的返回码或字面类型封装能支持复杂的错误封装类型则不行。noexcept 的异常传播和 std::terminate() 行为是实现依赖需注意。 “非平凡析构函数”non-trivial destructor是C术语简单说就是编译器不能自动生成的简单析构函数而是用户自定义的或编译器生成但比较复杂的析构函数。它有以下特点和影响 1. 平凡trivial析构函数 vs 非平凡析构函数 平凡析构函数trivial destructor 编译器自动生成没有任何用户自定义代码。不做任何操作直接释放对象内存即可。通常用于简单的、只含基本类型成员或不需要释放资源的类型。编译器可以进行一些优化比如允许对象放在只读内存区允许 constexpr 构造和析构。 非平凡析构函数non-trivial destructor 用户显式定义了析构函数或类含有成员变量自身的析构函数非平凡。需要执行用户代码比如释放动态资源内存、文件句柄等。编译器必须调用析构函数代码不能简单地忽略。这种析构函数使得类对象的生命周期管理更复杂。 2. 对 constexpr 的影响 C 标准要求 constexpr 析构函数必须是平凡的析构函数trivial destructor或者符合某些条件的 constexpr 析构函数。大多数标准库容器或包装类型如 std::optionalstd::expected因为需要管理资源和复杂状态它们的析构函数是非平凡的因此不支持在 constexpr 函数中使用特别是在C14之前。反过来如果一个类型的析构函数是平凡的且满足其他 constexpr 约束则可以用于 constexpr 函数和常量表达式。 3. 如何判断 一个类的析构函数是平凡的通常满足 没有自定义析构函数所有非静态数据成员的析构函数也是平凡的没有虚析构函数。 例如 struct A {int x;// 平凡析构函数 }; struct B {~B() {} // 用户自定义析构函数非平凡析构函数 }; struct C {std::string s; // std::string 的析构函数非平凡// 因此C的析构函数也是非平凡 };总结 非平凡析构函数意味着析构时需要执行额外代码无法被编译器简单忽略。这会影响类型能否用于 constexpr 函数中因为 constexpr 要求对象生命周期简单明确。这也是为什么 optional 和 expected 这种封装复杂状态的类型不能轻易用作 constexpr 的原因。 这部分内容重点讲了异常的设计目的、对代码路径的影响以及错误处理效率和代码清晰度的权衡我帮你总结理解 1. constexpr 与抛异常 带抛异常的 constexpr 函数是允许的比如C11起就支持只不过 在没有异常时函数可在编译期计算即常量表达式。一旦触发异常计算就转为运行时处理throw。 例如constexpr int integral_div(int num, int denom) {return !denom ? throw divide_by_zero{} : num / denom; }这种写法兼顾了编译期常量求值与运行时异常处理。 2. 异常设计目的错误检测与错误处理分离 检测错误的地方和处理错误的地方不一定是同一个。例如 integral_div 能检测除零但不负责决定“除零错误怎么办”。错误处理可能是 打印信息比如日志弹窗警告用户触发紧急停止核反应堆停止等 异常机制让错误处理从正常代码路径中剥离出来不用到处插入错误检查减少代码污染。 3. 错误处理如何影响代码路径的“干净度” 使用错误码HRESULT等和显式检查会导致“错误处理代码”穿插在正常流程中看起来很杂乱且容易遗漏处理。代码示例中演示了COM接口常用的HRESULT错误检查写法显得冗长且容易错。这是一种经典问题错误处理代码污染正常业务代码使代码难读难维护。Knuth建议用goto跳转简化错误处理ArmstrongErlang作者认为错误应并行处理不应污染主流程。 4. 错误处理永远不会消失 不论用异常、错误码还是其他机制错误都可能发生。不能假设“代码永远不会错”错误处理应设计得易于维护和阅读。异常就是为了解决这一痛点通过机制把错误处理从主流程里分离出来。 总结 constexpr 可以用异常异常路径在运行时执行非异常路径可编译时计算。异常的本质是将错误检测与错误处理分离减少正常代码的污染。传统错误码机制导致代码里充满检查和分支影响可读性和维护性。理想状态是错误处理“异步”于主流程主流程保持简洁。但是在现实中错误处理难以完全隐藏设计要兼顾效率、可维护性和正确性。 异常Exceptions的优缺点以及性能成本帮你总结和理解如下 Exceptions — 优点 不改变函数的自然接口 异常不会通过改变函数签名来处理错误除非加上 noexcept调用接口更干净。为异常情况提供独立代码路径 异常代码路径和正常代码路径分开不会污染主业务逻辑。将错误检测与错误处理分离 抛出异常只是通知错误发生具体怎么处理需要上下文环境决定。构造函数可以用异常传递错误 构造函数没有返回值用异常是传递错误的合理方式。 Exceptions — 缺点 并非人人喜欢异常机制 有人觉得异常机制复杂且难以管理。异常创建了另一条代码路径 这是优点也是缺点代码逻辑更复杂。异常有非零开销 主要是发生异常throw、catch时的成本不是正常执行路径的成本。异常可能被滥用 像语言中的其他特性一样使用不当会导致问题。异常和非异常安全代码的边界问题 例如与C语言、其他语言的库或工具交互时异常处理比较困难。Lippincott函数 是应对这种边界问题的一个工具。 Exceptions — 性能成本的实测与分析 多个测试案例比较了异常和无异常代码在不同情况下的性能 生成大量数据偶尔出错时抛异常与返回错误码的开销比较。多层递归函数调用时异常的堆栈展开成本。复杂数据结构vector, vectorvector下异常处理成本。错误频率不同频繁出现、偶尔出现、从不出现的性能对比。 结论是 异常的主要开销在于发生异常时的堆栈展开和异常处理代码执行 在正常执行路径无异常时性能影响较小。 理解总结 异常机制设计为正常路径开销小但错误路径开销可能大。错误不频繁时异常机制整体效率较高。异常代码路径增加代码复杂度但能让正常业务逻辑更清晰。异常和错误码各有利弊选择需权衡易用性、性能和代码可维护性。 这段内容主要介绍了标准库中异常的使用情况以及一个非常有趣的技巧——用异常机制做类型擦除和错误处理。 1. 标准库中的异常使用 标准库通常不主动抛异常主要是因为要保持效率和简洁。例外情况是 vector.at()它会在越界时抛 std::out_of_range。还有少数情况主要是内存分配失败时抛 std::bad_alloc。 2. 异常 错误 在 Boost.Graph 里有一个有趣的技巧来自 Caso Neri。这个技巧利用异常机制来实现类型擦除type erasure和错误管理解决了传统方法难以实现的“安全地从基类或子类转换”的问题。 3. “any_ptr” 类的实现和工作原理异常用作类型转换 any_ptr 类通过存储一个 void* 指针和两个函数指针用于抛出异常和销毁对象实现了类型擦除。关键函数是 thrower它会抛出存储的指针借助 C 的异常捕获机制捕捉特定类型的指针。cast_toT() 函数尝试通过抛出异常并捕获来“转换”指针类型返回正确类型的指针或者失败时返回 nullptr。析构时通过 destroyer 函数指针来释放资源。 这种方法看起来奇特但依赖异常机制来完成类型安全的转换是一种聪明的设计。 4. “自诊断异常”示例 代码中展示了一个有趣的用法 抛出一个函数指针lambda用于异常处理时执行特定的诊断操作。这种用法很少见也不推荐在生产代码中使用但很有创意。 using diag_t void(*)(ostream); int f(int n) {if (n 0)throw static_castdiag_t([]{ cout Ouch! endl; });return n; } int main() {try {cout f(-3) endl;} catch(diag_t diagnosis) {diagnosis();} }总结 标准库中的异常用得很少主要是边界检查和内存分配失败。异常可以被巧妙地用来实现类型擦除和安全转换尽管不常见。异常本身也能承载“行为”比如抛出一个函数指针来执行特定操作。
http://www.dnsts.com.cn/news/48367.html

相关文章:

  • 嘉兴网站制作彩票引流推广方法
  • 天津企悦在线网站建设建设银行青海省分行门户网站
  • 公司的网站建设公司网站建设今天上海新闻
  • 高端企业网站建设注意问题北京网站建设北京
  • 宁波公司建设网站摄影师常用的网站
  • 国外产品展示网站模板chrome 谷歌浏览器
  • 域名解析站长工具设计logo的手机软件免费
  • 大连建设工程招聘信息网站大数据营销分析
  • 网页设计和网站开发哪个好网站建设与管理策划书
  • 汽车网站 源码app的制作过程
  • 南通市规划建设局网站网页界面设计原则
  • 山东网站建设工作室wordpress 主题数据
  • 网站建设中颜色的感染力梧州网站开发
  • 给个网站你们知道的广州建设局网站
  • 网站怎么优化搜索域名抢注网站源码
  • 南昌网站建站怎样做网站挣钱
  • 正规品牌网站设计图片网站建设合同按什么交印花税
  • 南川网站建设辅助网站怎么做
  • 下什么软件做网站工信部网站备案怎么查
  • 做网站汉狮网络网站装修的代码怎么做
  • 网站对齐原则怎么看一个网站用什么语言做的
  • 取消网站备案流程wordpress 上传权限
  • 学校网站moodle ual wordpress
  • 上海 .net网站建设做的最好的视频教学网站
  • 陕西做网站的公司在哪阿里云官网
  • 建设一个网站的规划手机电子商务网站建设策划书
  • 做网站思路前端的网站重构怎么做
  • 网站关键词做排名不分wordpress实现自动重定向
  • 做网页用的网站即墨网站建设在哪
  • 免费建立单位的网站长沙品质企业建站服务电话