跳转到内容

C++ 编程/丢失的位

来自 Wikibooks,开放世界中的开放书籍

"安全布尔" 习语

[编辑 | 编辑源代码]

或为什么定义一个operator void*()强制转换运算符而不是一个operator bool()?

这样做是为了避免我们错误地写出诸如

int foo = std::cin;

或者,更重要的是,

int bah;
std::cin << bah;    // observe: << instead of >>

这样的语句。然而,它并不完美,因为它允许其他错误,例如

delete std::cin;

尽管幸运的是,此类错误不太可能发生,因为delete在任何情况下都应该谨慎使用。

最先进的做法是,我们应该在内部定义一个私有嵌套类dummystd::ios中,并返回 dummy 的指向成员函数的指针 - 从而允许从该指针到bool的隐式转换,但不允许许多其他操作。这有时被称为 "安全布尔" 习语,其动机是 C++ 的bool类型具有从int到 int 的隐式转换,这是标准化过程的结果。


Clipboard

要执行
"安全布尔" 习语可能更加清晰,或者如果存在相关信息,可以链接到其他相关信息。


auto关键字以前的行为有所不同,但在 C++ 中,它允许省略变量的类型,并让编译器决定。这对于泛型编程特别有用,在泛型编程中,函数的返回类型可能取决于其参数的类型。因此,我们无需写成这样

int x = 42;
std::vector<double> numbers;
numbers.push_back(1.0);
numbers.push_back(2.0);
for(std::vector<double>::iterator i = numbers.begin();
    i != numbers.end(); ++i) {
  cout << *i << " ";
}

可以写成这样

auto x = 42; // We can use auto on base types...
std::vector<double> numbers;
numbers.push_back(1.0);
numbers.push_back(2.0);
// But auto is most useful for complicated types.
for(auto i = numbers.begin(); i != numbers.end(); ++i) {
  cout << *i << " ";
}

带有 源代码示例的作用域

[编辑 | 编辑源代码]

作用域的概念很简单,除非包含过程;然后,就很难跟踪了

// Confusing Scope Program
#include <iostream>

using namespace std;

int i = 5;           /* The first version of the variable 'i' is now in scope */

void p(){
  int i = -1;        /* A ''new'' variable, also called 'i' has come into existence. The 'i' declared above is now out of scope,*/
  i = i + 1;
  cout << i << ' ';  /* so this ''local'' 'i' will print out the value 0.*/
}                    /* The newest variable 'i' is now out of scope. The first variable, also called 'i', is in scope again now.*/

int main(){
  cout << i << ' ';  /* The first variable 'i' is still in scope here so a ''5'' will be output here.*/
  char ch;
  int i = 6;         /* A ''new'' variable, also called 'i' has come into existence. The first variable 'i' is now out of scope again,*/
  i = i + 1;
  p();
  cout << i << endl; /* so this line will print out a ''7''.*/
  return 0;
}                    /* End of program: all variables are, of course, now out of scope.*/

第一个变量 'i' 在两个不同的部分被置于作用域之外。因此,重复的语句 cout << i << ' '; 每次写入都表示不同的含义。每次提到的 'i' 都是内存中的不同位置。这就是引言中提到的 `上下文':语句所处的上下文或背景每次都不一样,因此语句每次在不同的地方执行不同的操作。

关于上面的示例,有一些重要的注意事项。该程序只是一个示例,并且非常复杂,它只用于演示作用域的概念,而不是其他任何东西。虽然它说明了作用域的概念,但它并没有有效地说明作用域的用途。

作用域的用途

[编辑 | 编辑源代码]

某些变量需要存储整个程序的信息,而其他变量是短期变量,它们只为单个小目的而短暂地存在,然后通过超出作用域而被销毁。在以下程序中,读取一组数字,然后调用一个过程,该过程计算数组中数字的平均值。在过程中,为了遍历数组并依次选择数组中的元素,会创建一个名为 'i' 的变量,用于执行此目的。对比两种类型的变量:数组本身在整个程序中始终处于作用域内,而变量 'i' 仅在代码的一小部分中处于作用域内,以执行其自身的小任务。

// Program Average
#include <iostream>

using namespace std;

float a[10];                      /* a is now in scope.*/
int length;                       /* length is now in scope.*/

float average(){
  float result = 0.0;             /* result is now in scope.*/

  for(int i = 0; i < length; i++){ /* i is now in scope.*/
    result += a[i];
  }                               /* i is now out of scope.*/

  return result/length;
}                                 /* result is now out of scope.*/

int main(){
  length = 0;
  cout << "enter a number for each of the next 10 lines" << endl;

  while( length != 10 )
    { cin >> a[length++]; }

  float av = average();            /* av is now in scope.*/
  cout << endl << "average: " << av << endl;

  return 0;
}                                 /* All variables now out of scope.*/

作用域和控制结构

[编辑 | 编辑源代码]

在一个过程中,可以开始一个新的作用域级别。实际上,每次写入左花括号 `{' 时都会发生这种情况,并且在写入与其匹配的右花括号 '}' 的地方结束。因此,可以构建任意深度的作用域层,如以下程序所示。以下程序具有四个作用域级别。最内层的作用域被认为是 包含 其周围的作用域,因此我们谈论内层作用域和外层作用域。同样,该程序仅用于说明目的,它没有任何实际价值。

// Complicated Scope Program
#include <iostream>
  
using namespace std;  /* outermost level of scope starts here */

int i;

int main(){               /* next level of scope starts here */
  int i;
  i = 5;

  {                   /* next level of scope starts here */
    int j,i;
    j = 1;
    i = 0;
  
    {                 /* innermost level of scope of this program starts here */
      int k, i;
      i = -1;
      j = 6;
      k = 2;
    }                 /* innermost level of scope of this program ends here */

    cout << j << ' ';
  }                   /* next level of scope ends here */

  cout << i << endl;
  return 0;
}                     /* next and outermost levels of scope end here */

该程序的输出是 6 5。为了理解原因,我们首先查看 'i' 的更简单情况,并了解为什么为它打印了 5,然后查看 'j' 的更复杂情况,以及为什么为它打印了 6。

在每个新的作用域级别,都会创建一个新的变量 'i'。因此,仅第一次对 'i' 的赋值(其中 'i' 被赋值为 5)会影响这个特定的变量 'i':即被打印的变量。该变量在第一次赋值后不会改变其值,因此最终的语句打印了一个 5。对 'i' 的其他赋值与最终的打印语句无关。

相反,变量 'j' 仅创建一次,因此,即使变量是在外层作用域中声明的,将 `j' 赋值为 6 的赋值也会改变这个唯一的现有变量 'j'。如果程序有一个在另一个作用域级别内的作用域级别,并且在该内层作用域级别没有声明具有相同名称的变量,那么计算机将 `向外查看' 到下一个外层作用域级别。如果那里也没有声明具有该名称的变量,那么它将继续向外查看,直到找到变量的声明。(当然,如果从未找到变量的声明,那么编译器将指示错误,说明变量未声明,因此程序不会被编译。)

使用其他控制结构的作用域

[编辑 | 编辑源代码]

上面我们说过,每个左花括号 `{'. 都会开始一个新的作用域级别。然而,不常用裸左花括号:通常,左花括号与if 语句或while 语句或类似语句相关联。我们在上面的程序中添加了这些语句,以创建一个更常见的(但仍然无用)示例程序。该程序可以编译,并且在运行时打印一个 5,但作为一个程序几乎没有价值。

// Complicated Scope Program, variation 1
#include <iostream>

using namespace std;  /* outermost level of scope starts here */

int i;

int main(){               /* next level of scope starts here */
  int i;
  i = 5;

  while(i != 5) {     /* next level of scope starts here */
    int j,i;
    j = 1;
    i = 0;
    switch (i) {      /* next level of scope starts here */
      int i;

      case 1:
        if (i != 4) { /* innermost level of scope of this program starts here */
          int k, i;
          i = -1;
          j = 6;
          k = 2;
        }             /* innermost level of scope of this program ends here */
        break;

      case 2: 
        j = 5;
        break;
    }                 /* next level of scope ends here */

    cout << j << ' ';
  }                   /* next level of scope ends here */

  cout << i << endl;
  return 0;
}                     /* next and outermost levels of scope end here */

我们在switch 语句中添加了一个额外的作用域级别,因为该语句要求一个左花括号,并且它表明即使在这一点上,也会打开一个新的作用域级别(因为我们能够声明另一个名为 `i' 的变量而不会出现错误)。如您所见,各种控制结构(即while、ifswitch 语句)都在其各自的匹配右花括号处结束,因此与作用域结束的点相同。可以公平地说,每个控制语句都在作用域的某个区域内运行。但请记住,作用域是一个关于变量名称及其定义区域的概念,而不是关于控制结构的概念。

ifwhile 语句在其后具有一个开括号并不是必需的,程序 average 上面展示了其while 语句的示例。在这种情况下,不会开始新的作用域级别。是左花括号打开了新的作用域级别,而不是ifwhile 语句本身。switch 语句要求在该点处有一个左花括号,但请注意,`i' 开关变量在旧的作用域级别。新的作用域级别在左花括号之后立即出现,一如既往。

在实际应用中,for 循环控制结构的范围作用方式与其他结构类似。然而,允许在 for 语句本身内声明 for 循环变量,如上面program average中的第八行所示。这是一种良好的编程习惯,因为它使程序结构更加清晰。

在本节中的所有程序中,语句随着作用域级别的加深而向右延伸。这被称为缩进,是程序呈现中非常重要的一个特性。它始终明确地显示作用域级别,并清楚地标明语句的结束位置。例如,在上面的程序中,cout << j << ' '; 位于while循环内,而cout << i << endl; 则位于循环之外。循环和作用域级别在同一位置结束:这两个语句之间的右括号表示两者都已结束。

详细介绍 for 控制语句的作用域

[edit | edit source]

本小节可能比有用更令人困惑,但为了完整性而提供;您可以随意跳过它。

for 控制语句具有不寻常的作用域,即左括号也开始它自己的作用域级别。因此,以下程序是合法的

// Complicated Scope Program, variation 2
#include <iostream>
  
using namespace std; /* outermost level of scope starts here */

int i;

int main(){              /* next level of scope starts here */
  int i;
  i = 5;

  for(               /* next level of scope starts here */
      int i = 1;
      i<10 && cout << i << ' ';
      ++i )  
  {                  /* next level of scope starts here */
    int i = -1;
    cout << i << ' ';
  }                  /* two levels of scope end here*/

  cout << i << endl;
  return 0;
}                    /* next and outermost levels of scope end here */

它输出

 1 -1 2 -1 3 -1 4 -1 5 -1 6 -1 7 -1 8 -1 9 -1 5

这种 for 语句的特殊特性不是while 语句所共有的。尝试在while语句内声明变量是语法错误,因此while(int i < 22)i++; 会引发语法错误。

这种特殊的范围级别使我们能够在 for 循环本身内声明一个 for 循环变量(例如上面程序中的变量 'i'),而不是必须在包含的作用域级别声明它,从而创建一个更简洁的程序。但这有点特殊。

上面的程序确实显示了一个有趣的特性:为了检查是否打开了新的作用域级别,只需尝试在该级别再次声明一个已存在的变量即可,就像上面示例程序中对变量 'i' 所做的那样,它在每个可能的级别都声明了一个新的变量 'i'。

上面的程序还说明了另一个非常重要的点:计算机编程中有一句谚语,即可以用任何语言编写糟糕的代码。上面的程序在操作方面相当不清楚,并且是糟糕编码的典型例子:它适合说明一个观点,但不适合阅读。所有代码都应尽可能清晰地编写,使程序尽可能清晰,正如其他地方关于程序风格的讨论中所述。

作用域和生命周期

[edit | edit source]

变量的作用域应与其生命周期区分开来。在上面名为 'Confusing Scope Program' 的程序中,第一个变量 'i' 在一段时间内不再处于作用域内,但它仍然存在,因此它的生命周期仍在继续,尽管它不在作用域内。在旧的编程语言中,很难构造出作用域和生命周期不同的例子——通常情况下,在这些旧语言中,两者是相同的,因此生命周期等于作用域。不仅很难,而且在它确实发生的一般情况下也没有那么有用。然而,在最近创建的计算机语言(如 C++)中,使用超出作用域但仍然存活的变量的想法得到了广泛应用,并创造了 C++ 的主要区别特征:C++ 。这是上面程序用类重写的版本

// Program Average rewritten using a class
#include <iostream>

using namespace std;

class StatisticsPackage{
private:
  float aa[20];                     /* aa scope start*/
  int length;                       /* length scope start*/
public:                           
  float average(){
    float result = 0.0;             /* result scope start*/
 
    for(int i = 0; i < length; ++i) /* i scope start*/
      result += aa[i];
 
  return result/length;
  }                                 /* result and i scope end*/

  void get_data(){
    length = 0;
    while(cin >> aa[length++]);
    --length;
  }
};                                  /* aa and length scope end*/

int main(){
  StatisticsPackage sp;             /* aa and length lifetimes start */
  sp.get_data();
  float av = sp.average();          /* av scope start*/
  cout << av << endl;
  return 0;
}                                   /* av scope end*/

在这个版本的程序中,变量 'length' 和 'aa' 在类 'sp' 出现后仍然存活。但是,它们的范围已经限制了:它们是存活的,但不在作用域内,存储了信息,但不能在主程序中直接访问。以这种方式将变量保持在作用域之外对调试程序非常有用,因为它缩小了可能更改变量值的行的数量。


Clipboard

要执行
演变并添加对类空间等部分中进一步见解和实用性的引用。


strcat_s

[edit | edit source]

strcat_s 函数被提议用作 strcat 函数的近似替代品。strcat_s strcat 多一个参数,该参数指定目标字符串的最大大小。strcat_s 函数将源字符串的字符追加到第一个字符串目标字符串的末尾

语法
#include <string.h>
errno_t strcat_s(char *  dest,rsize_t s1max,const char * scr);

例如

char str1 , str2;
printf( "Enter your first string : " );
scanf( "%s", str1 );
printf( "Enter your second string: " );
scanf( "%s", str2);
strcat_s(str1, 16, str2);  
printf( " %s\n", str1 );


Clipboard

要执行
可能可以将链接添加到标准 strcat() 函数中,并专门介绍 Microsoft C Runtime Library 和其他一些 C 库的特殊性。目前还没有足够的资料或兴趣来进行此操作。

华夏公益教科书