CPP 9. 内存模型和名称空间 2020-11-05 浏览量 557 暂无评论 # 第9章 内存模型和名称空间 ## 9.1 单独编译 一般将一个大型文件分解为三部分: - 头文件:包含结构声明和使用这些结构的函数的原型(通常有函数原型、使用#define或const定义的符号常量、结构声明、类声明、模板声明、内联函数,不能将函数定义放在头文件中,当同一个程序的两个文件都包含了这个头文件时,相当于包含了同个函数的两次定义,将出错); - 源代码文件:包含与结构有关的函数的代码; - 源代码文件:包含调用与结构相关的函数的代码。 🐸小知识: 1. 在包含自己的头文件时,应使用引号而不是尖括号(首先查找当前的工作目录或源代码目录,而不是在标准位置); **示例** ```cpp // coordin.h // 使用ifndef避免在同一个文件中包含同一个头文件多次 // (当包含的文件中也包含该头文件时会出现这种情况) #ifndef COORDIN_H_ #define COORDIN_H_ struct polar { double distance; double angle; }; struct rect { double x; double y; }; polar rect_to_polat(rect xypos); void show_polar(polar dapos); #endif // file1.cpp #include #include "coordin.h" using namespace std; int main() { rect rplace; polor pplace; cout << "Enter the x and y values: "; while (cin >> rplace.x >> rplace.y) { pplace = rect_to_polar(repalce); show_polar(pplace); cout << "Next two numbers (q to quit): "; } cout << "Bye!\n"; return 0; } // file2.cpp #include #include #include "coordin.h" polar rect_to_polar(rect xypos) { using namespace std; polar answer; answer.distance = sqrt(xypos.x * xypos.x + xypos.y * xypos.y); answer.angle = atan2(xypos.y, xypos.x); return answer; } void show_polar(polar dapos) { using namespace std; const double Rad_to_deg = 57.29577951; cout << "distance = " << dapos.distance; cout << ", angle = " << dapos.angle * Rad_to_deg; cout << " degrees\n"; } ``` ## 9.2 存储持续性、作用域和链接性 **存储持续性:** 自动存储持续性、静态存储持续性、线程存储持续性(C++11)、动态存储持续性。 **作用域(scope):** 名称在文件的多大范围内可见,分为局部和全局(名称空间作用域的特例)。 **链接性(linkage):** 名称如何在不同文件间共享,分为无、外部和内部。 ```cpp int global = 1000; // 外部链接,程序的其他文件中也可以使用 static int one_file = 50; // 内部链接,只在该文件中可以使用; int main() { ... } void funct1(int n) { static int count = 0; // 无链接静态变量,只在该函数中能访问,但在程序整个运行期间都在内存中 int llama = 0; // 自动变量 ... } ``` 🐸小知识: 1. 默认情况下,函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性,存储在栈中,后进先出; 2. 关键字auto和register的演变历史(之前的用法现在不用了,但不非法); 3. 虽然无链接性的静态持续变量和自动变量都是只能在当前函数或代码块中访问,但是自动变量不会像静态持续变量那样在整个程序执行期间存在,适用于再生; 4. 单定义规则:如果要在多个文件中使用**外部变量**,只需在一个文件中包含该变量的定义,但在使用该变量的其他所有文件中,都必须使用关键字extern声明它; 5. 作用域解析运算符"::",放在变量名前面表示使用变量的全局版本; 6. 虽然使用全局变量可以不用传递参数,但这样的程序不可靠,通常情况下,应使用局部变量,外部存储适于表示常量数据; 7. const全局变量的链接性为内部; ```cpp // 第一个const防止字符串被修改,第二个字符串防止每个指针的位置被修改 const char * const months[12] = { "January", "February", "March", ..., "December" } ``` 8. 一个文件中与另一文件中常规外部变量的同名静态外部变量将隐藏常规外部变量; ### 9.2.1. 说明符和限定符 **a. c-v限定符** - const - volatile。表明即使程序代码没有对内存单元进行修改,其值也可能发生变化,比如串口通信中,硬件可能修改其中的内容。它的作用是改善编译器的优化能力,防止这种变量被错误优化。 **b. mutable** 用来指出即使结构(或类)变量为const,其某个成员也可以被修改。 ### 9.2.2. 函数的链接性 1. 默认情况下,函数的链接性为外部,可以在不同的文件中使用;还可以用关键字static将函数的链接性设置为内部的,在定义静态函数的文件中,静态函数将覆盖外部定义。 2. 单定义规则也适用于非内联函数,这意味着**在多文件程序中,只能有一个文件包含该函数的定义(该文件可能是库文件)**; 3. 内联函数的定义可以放在头文件中; 4. C++查找函数的顺序:(若静态)本文件-->所有程序文件-->库; ### 9.2.3. 语言链接性 C语言和C++编译器为函数名重命名的方式不同,因此在C++中想要使用C库文件中预编译的函数时,需要指定使用C语言链接性。 ```cpp extern "C" void spiff(int); // C extern void spoff(int); // C++ extern "C++" void spaff(int); // C++ ``` ## 9.3 名称空间 名称空间是为了解决大项目中的名称冲突而出现的。 ```cpp namespace A { double pail; void fetch(); } // 名称空间是开放的,即可以把名称加入到已有的名称空间中 namespace A { int haha; } namespace B { int pail; double featch; } // 名称空间可以嵌套 namespace C { namespace D { int a; ... } float water; } // 可以给名称空间创建别名 namespace ABC = A::B::C; // 限定的名称 A::haha b::pail // 如果不想每次使用都进行限定怎么办呢?两种办法 // 1. using声明,使特定的标识符可用 using A::haha; // 2. using编译指令,使整个名称空间可用 using namespace A; #include // 将名称加入名称空间std using namespace std; // 使名称全局可用 ``` ## 9.4 总结 尽量将变量、函数库、类库写在名称空间中,多方便,多优雅。 - 阅读全文 -
CPP 8. 函数探幽 2020-11-03 浏览量 570 暂无评论 # 第8章 函数探幽 ## 8.1 C++内联函数 🐸小知识: 1. 内联函数是C++为提高程序运行速度所做的一项改进,具体是怎么提高运行速度的呢? **调用常规函数:** 存储调用指令的地址-->复制函数参数至堆栈-->跳到标记函数起点的内存单元并执行函数代码(也许还需将返回值放入到寄存器中)-->调回地址被保存的调用指令处。 **使用内联函数:** 编译器使用相应的函数代码替换函数调用,无需跳来跳去,以占用更多内存为代价。 2. 内联函数不能递归; 3. 使用规则:要么在函数声明前加上关键字inline,要么在函数定义前加上关键字inline,通 常省略原型,将整个定义放在本应提供原型的地方; 4. 大函数不适合用内联,因为本身执行时间就很长; 5. C++内联函数可以按值传递,但C语言的宏定义`#define SQUARE(X) ((X)*(X))`不可以按值传递; **示例:** ```cpp #include inline double square(double x) {return x * x;} int main() { using namespace std; double a, b; double c = 13.0; a = square(5.0); b = square(4.5 + 7.5); cout << "a = " << a << ", b = " << b << endl; cout << "c = " << c; cout << ", c square = " << square(c++) << endl; } ``` ## 8.2 引用变量 引用是一种复合类型,它是已定义的变量的别名,与原变量指向相同的值和地址; 🐸小知识: 1. 通过将引用变量用作参数,函数将使用原始数据,而不是其副本; 2. 引用必须在声明时将其初始化,更接近const指针; 3. 在用作函数参数时,尽可能使用const; 4. 返回引用时直接把返回值复制给接收变量,而不用创建临时变量(应避免返回临时变量的引 用或指针); 5. 返回引用的函数实际上式被引用的变量的别名; **创建引用变量** ```cpp int rats; int & rodents = rats; // 让rodents成为rats的别名 ``` **将引用用作函数参数** ```cpp #include void swap(int & a, int & b); int main() { using namespace std; int wallet1 = 300; int wallet2 = 350; swap(wallet1, wallet2); } void swap(int & a, int & b) { int temp; temp = a; a = b; b = temp; } ``` **临时变量、引用和const** C++将在两种情况下生成临时变量(仅当参数为const时,C++才允许这样): 1. 实参的类型正确,但不是左值; 2. 实参的类型不正确,但可以转换为正确的类型。 临时变量并不会影响原变量。 ```cpp double refcube(const double &ra) { return ra * ra * ra; } double side = 3.0; double * pd = &side; double & rd = side; long edge = 5L; double lens[4] = {2.0, 5.0, 10.0, 12.0}; double c1 = refcube(side); double c2 = refcube(lens[2]); double c3 = refcube(rd); double c4 = refcube(*pd); double c5 = refcube(edge) // ra is temporary variable, case 2 double c6 = refcube(7.0); // ra is temporary variable, case 1 double c7 = refcube(side + 10.0); // ra is temporary variable, case 1 ``` **右值引用** 这种引用可指向右值,使用&&声明。 ```cpp double && rref = std::sqrt(36.00); double j = 15.0; double && jref = 2.0 * j * 18.5; std::out << rred << endl; std::out << jred << endl; ``` **将引用作为函数的返回值** ```cpp // 返回string const string v1(const string & s1, const string & s2) { string temp; string = s2 + s1 +s2; return temp; } // 返回string的引用 const string & v2(string & s1, const string & s2) { s1 = s2 + s1 + s2; return s1; } ``` **对象、继承和引用** 1. 派生类继承基类的方法; 2. 基类引用可以指向派生类对象。 注意下面的程序:参数os(类型为ostream &)可以指向ostream对象(如cout),也可以指向ostream的派生类ofstream对象(如fout)。 ```cpp #include #include #include using namespace std; void file_it(ostream & os, double fo, const double fe[], int n); const int LIMIT = 5; int main() { ofstream fout; const char * fn = "ep-data.txt"; fout.open(fn); if (!fout.is_open()) { cout << "Can't open " << fn << ". Bye.\n"; exit(EXIT_FAILURE); } double objective; cout << "Enter the focal length of your " "telescope objective in mm: "; cin >> objective; double eps[LIMIT]; cout << "Enter the focal lengths, in mm, of " << LIMIT << " eyepieces: \n"; for (int i = 0; i < LIMIT; i++) { cout << "Eyepiece #" << i + 1 << ": "; cin >> eps[i]; } file_it(fout, objective, eps, LIMIT); file_it(cout, objective, eps, LIMIT); cout << "Done\n"; cin.get(); cin.get(); return 0; } void file_it(ostream & os, double fo, const double fe[], int n) { ios_base::fmtflags initial; initial = os.setf(ios_base::fixed); os.precision(0); os << "Focal length of objective: " << fo << " mm\n"; os.setf(ios::showpoint); os.precision(1); os.width(12); os << "f.1. eyepiece"; os.width(15); os << "magnification" << endl; for (int i = 0; i < n; i++) { os.width(12); os << fe[i]; os.width(15); os << int (fo/fe[i] + 0.5) << endl; } os.setf(initial); } ``` **何时使用引用参数:** 1. 修改调用函数中的数据对象; 2. 提高程序的运行速度; ## 8.3 默认参数 🐸小知识: 1. 通过函数原型设置默认值`char * left(const char * str, int n = 1);`,必须从右向左添加默认值; 2. ***在设计类时通过使用默认参数,可以减少要定义的析构函数、方法以及方法重载的数量;*** ## 8.4 函数重载(函数多态) C++在C语言的基础上新增的功能。 特征标:函数的参数列表,包括参数数目、类型、排列顺序。 🐸小知识: 1. 默认参数允许使用不同数目的参数调用同一个函数,而函数多态允许使用多个同名的函数(**那C++怎么知道是在用同一个函数的默认参数还是多态的同名的函数呢?函数重载的条件是它们的特征标不同**); 2. 从编译器的角度考虑特征标; 3. 没有匹配的原型并不会自动停止使用其中的某个函数,因为C++将尝试使用标准类型转换强制进行匹配,但如果有多个函数满足强制匹配,则C++将其视为错误; 4. 匹配函数时,并不区分const和非const变量; ```cpp void dribble(char * bits); void dribble(const char *cbits); void dabble(char * bits); void drivel(const chat * bits); const char p1[20] = "How's the weather?"; char p2[20] = "How are you?"; dribble(p1); // dribble(const char *) dribble(p2); // dribble(char *) dabble(p1); // no match dabble(p2); // dabble(char *) drivel(p1); // drivel(const char *) drivel(p2); // drivel(const char *) ``` 5. 重载引用参数时,将调用最匹配的版本(参数是左值、const还是右值); 6. C++怎么分得清到底调用哪个 **何时使用函数重载** 仅当函数基本上执行相同的任务,但使用不同类型的参数时,才应使用函数重载,如果只是参数数量不同,可以考虑默认参数。 ## 8.5 函数模板 函数模板就是使用泛型来定义函数,其中的泛型可以用具体的类型来替换,编译器在编译的时候会将泛型替换为具体的类型。 🐸小知识: 1. 并非所有的模板参数都必须是模板参数类型; 2. 在原型声明时需要加上`template `; 3. 函数模板虽然试图使用泛型来简化代码,但是它仍然不能处理所有数据类型,比如常规变量 的`a=b`不能用于数组,`a*b`不能用于指针参数等,所以有必要使用不同的原型,那怎么分清使用哪个原型呢?这时候就要涉及到原型优先级:非模板函数>具体化>常规模板(见下面示例); 4. 既然可以隐式实例化,显式实例化有什么用呢?加快运行速度? ```cpp template // template void Swap(AnyType &a, AnyType &b) { AnyType temp; temp = a; a = b; b = temp; } ``` **具体化** ```cpp // non template function prototype, job is a struct void Swap(job &, job &); // template prototype template void Swap(T &, T &); // explicit specialization for the job type template <> void Swap(job &, job &); template <> void Swap(job &, job &); ``` **原型优先级** ```cpp #include using namespace std; void Swap(int &a, int &b); // 隐式实例化(explicit instantiation) template void Swap(T &a, T &b); // 显式实例化(explicit instantiation) template void Swap(int, int); // 显示具体化(explicit specialization),不能和显示实例化同时使用 template <> void Swap(job &j1, job &j2); void Show(job &j); struct job { char name[40]; double salary; int floor; }; int main() { cout.precision(2); cout.setf(ios::fixed, ios::floatfield); int i = 10, j = 20; cout << "i, j = " << i << ", " << j << ".\n"; cout << "Using compiler-generated int swapper:\n"; Swap(i, j); cout << "Now i, j = " << i << ", " << j << ".\n"; job sue = {"jhan", 4235454.76, 7}; job sidney = {"xyhan", 32424.56, 9}; cout << "Before job swapping:\n"; Show(sue); Show(sidney); Swap(sue, sidney); cout << "After job swapping:\n"; Show(sue); Show(sidney); cin.get(); return 0; } void Swap(int &a, int &b) { cout << "Enter regular Swap.\n"; int temp; temp = a; a = b; b = temp; } template void Swap(T &a, T &b) { cout << "Enter template Swap.\n"; T temp; temp = a; a = b; b = temp; } template <> void Swap(job &j1, job &j2) { cout << "Enter specification Swap.\n"; double t1; int t2; t1 = j1.salary; j1.salary = j2.salary; j2.salary = t1; t2 = j1.floor; j1.floor = j2.floor; j2.floor = t2; } void Show(job &j) { using namespace std; cout << j.name << " : $" << j.salary << " on floor " << j.floor << endl; } 输出: i, j = 10, 20. Using compiler-generated int swapper: Enter regular Swap. Now i, j = 20, 10. Before job swapping: jhan : $4235454.76 on floor 7 xyhan : $32424.56 on floor 9 Enter specification Swap. After job swapping: jhan : $32424.56 on floor 9 xyhan : $4235454.76 on floor 7 ``` **🌈原型的匹配及完全匹配(难点):** 在函数重载、函数模板、函数模板重载时,C++按以下过程决定使用哪个函数: 1. 创建候选函数列表。这里放名称一样的函数和模板函数; 2. 使用候选函数列表创建可行函数列表。这里是参数数目正确的函数; 3. 确定是否有最佳的可行函数。如果有,就使用它,否则调用出错。 从最佳到最差的顺序如下: 1. 完全匹配,但常规函数优先于模板函数; 2. 提升转换(如char和shorts自动转换为int,float自动转换为double); 3. 标准转换(如int转换为char,long转换为double); 4. 用户定义的转换,如类声明中定义的转换。 原型的完全匹配允许一些无关紧要的转换,如下表: 从实参|到形参 ---|--- Type|Type & Type &|Type Type[]|* Type Type (argument-list)|Type (*) (argument-list) Type|const Type Type|volatile Type Type *|const Type Type *|volatile Type * ```cpp #include template T lesser(T a, T b) { return a < b ? a : b; } int lesser (int a, int b) { a = a < 0 ? -a : a; b = b < 0 ? -b : b; return a < b ? a : b; } int main() { using namespace std; int m = 20; int n = -30; double x = 15.5; double y = 25.9; cout << lesser(m, n) << endl; // use #2 cout << lesser(x, y) << endl; // use #1 with double // 指出应使用模板函数 cout << lesser<>(m, n) << endl; // use #1 with int // 进行显示实例化 cout << lesser(x, y) << endl; // use #1 with int return 0; } ``` **关键字decltype和后置返回类型(C++11)** 在C++98中,有时无法确定编写的模板函数中用到的中间变量类型,这时候decltype就派上了用场(像numpy中的astype)。 ```cpp int x; decltype(x) y; decltype(x + y) xpy = x + y; double x = 5.5; decltype ((xx)) r = xx; // r2 is double & decltype (xx) w = xx; // w is double auto h(int x, float y) -> double; template auto gt(T1 x, T2 y) -> decltype(x + y) { ... return x + y; } ``` - 阅读全文 -
CPP 7. 函数--C++的编程模块 2020-10-17 浏览量 452 暂无评论 # 第7章 函数--C++的编程模块 ## 7.1 自定义函数 使用自定义函数:定义、提供原型、调用。 🐸小知识: 1. 如果声明的返回类型为double,而函数返回一个int表达式,则该int值将被强制转换为double类型; 2. C++的返回值类型可以是除数组外的其他任何类型,如整数、浮点数、指针,甚至可以是结构和对象(C++函数虽然不能直接返回数组,但可以将数组作为结构或对象组成部分来返回); 3. 函数通过将返回值复制到指定的CPU寄存器或内存单元中来将其返回,随后,调用程序将查看该内存单元; 4. 如果函数包含多条返回语句,则函数在执行遇到的第一条返回语句后结束; ### 7.1.1 为什么需要原型 原型描述了函数到编译器的接口,它将函数返回值的类型以及参数的类型和数量告诉编译器。 ### 7.1.2 原型的语法 ```cpp double cube(double x); void cheers(int); void say_hi(); // 函数没有参数 void say_bye(...); // 不指定参数列表(接受可变参数) ``` 函数原型不要求提供变量名,有类型列表就够了,原型中的变量名相当于占位符,因此不必与函数定义中的变量名相同。 ## 7.2 函数参数和按值传递 实参(参数argument):传递给函数的值; 形参(参量parameter):用于接收传递值得变量; 🐸小知识: 1. 如果函数的两个参数类型相同,则必须分别指定每个参数的类型; 2. 函数内定义的变量是局部变量; ```cpp // 交替进行乘除的策略 long double probability(unsigned numbers, unsigned picks) { long double result = 1.0; long double n; // 为什么要将n声明为long double呢? unsigned p; for (n = numbers, p = picks; p > 0; n--, p--) result = result * n / p; return result; } ``` ## 7.3 函数和数组 🐸小知识: 1. 当且仅当用于函数头或函数原型中,`int * arr和int arr[]`的含义才是相同的; 2. 传递常规变量时,函数将使用该变量的拷贝,但传递数组时,函数将使用原来的数组(因为传递的其实是一个地址,这也是为什么传递数组名的同时还要传递长度); 3. 可在声明形参时使用关键字const防止函数无意中修改数组的内容; ```cpp void f_modify(double ar[], int n); void _f_no_change(const double ar[], int n); ``` ```cpp #include const int ArSize = 8; int sum_arr(int arr[], int n); int main() { using namespace std; int cookies[ArSize] = {1, 2, 4, 8, 16, 32, 64, 128}; int sum = sum_arr(cookies, ArSize); cout << "Total cookied eaten: " << sum << endl; cin.get(); return 0; } int sum_arr(int arr[], int n) // int sum_arr(int * arr, int n) { int total = 0; for (int i = 0; i < n; i++) total = total + arr[i]; return total; } ``` ### 7.3.1 指针和const 🐸小知识: 1. 指向const的指针,不能通过指针修改其所指向的值;指针本身是const,不能修改指针指向的位置; 2. 尽可能指针参数声明为指向常量数据的指针,因为: a. 这样可以避免由于无意间修改数据而导致的编程错误; b. 使用const使得函数能够处理const和非const实参,否则将只能接受非const数据。 ```cpp int age = 23; //pt指向一个const int,即*pt的值为const const int * pt = &age; // 可以通过age修改age,但不能通过pt修改age const float g_earth = 9.80; const float * pe = &g_earth; // valid float * pm = &g_earth; // invalid float * const pn = &g_earth; // 指针本身是const,不能修改,但可使用pn修改g_earth的值 // 简单来说:pn和*pe是const,而*pn和pe不是 ``` 🌈难点: 仅当只有一成间接关系(如指针指向基本数据类型)时,才可以将非const地址或指针赋给const指针。 ```cpp const int **p2; int *p1; const int n =13; pp2 = &p1; // not allowed, but suppose it were *pp2 = &n; // valid, both const, but sets p1 to point at n *p1 = 10; // valid, but change const n ``` ## 7.4 函数和二维数组 原型:`int sum(int (*ar2)[4], int size);`,只能接受由4列组成的数组,行数可变,等价于`int sum(int ar2[][4], int size);` ## 7.5 函数和C-风格字符串 C-风格字符串由一系列字符组成,以空值字符结尾。(包含字符,但不以空值字符结尾的char数组只是数组,而不是字符串,因此不必将字符串长度作为参数传递给函数) ```cpp // 处理字符串中字符的标准方式 while (*str) { statements str++; } #include char * buildstr(char c, int n); int main() { using namespace std; int times; char ch; cout << "Enter a character: "; cin >> ch; cout << "Enter an integer: "; cin >> times; char *ps = buildstr(ch, times); cout << ps << endl; delete [] ps; cin.get(); cin.get(); return 0; } char * buildstr(char c, int n) { char * pstr = new char[n+1]; pstr[n] = '\0'; while(n--) pstr[n] = c; return pstr; } ``` 注意: 变量pstr的作用域为buildstr函数内,因此当该函数结束时,pstr使用的内存将被释放,但new新建的字符串还在,由于函数返回了pstr的值,因此程序仍可以通过main()中的指针ps来访问新建的字符串。 ## 7.6 函数和结构 结构可使用按值传递,但是如果结构非常大,按值传递将增大内存要求,降低系统运行的速度,所以出现了另外两种方式:传递结构的地址和按引用传递。 ```cpp // 输入两个数字 int x, y; while (cin >> x >> y) { statements ... } ``` ## 7.7 函数和string、array对象 在C++中,类对象是基于结构的。 ## 7.8 递归 在需要将一项工作分为两项较小的、类似的工作时,递归非常有用。 递归方法有时被称为分而治之策略。 ## 7.9 函数指针 函数的地址:函数名 声明函数指针: ```cpp double pam(int); double (*pf)(int); pf = pam; double x = pam(4); double y = (*pf)(4); double z = pf(4); // 函数指针数组 const double * (*pa[3])(const double *, int) = {f1, f2, f3}; // auto只能用于单值推断 // 使用typedef简化声明 typedef const double *(*p_fun)(const double *, int); p_fun p1 = f1; p_fun p3 = {f1, f2, f3}; ``` - 阅读全文 -
CPP 6. 分支语句和逻辑运算符 2020-10-17 浏览量 533 暂无评论 # 第6章 分支语句和逻辑运算符 ## 6.1 if语句 ```cpp #include int main() { char ch; std::cout << "Type, and I shall repeat.\n"; std::cin.get(ch); while (ch != '.') { if (ch == '\n') std::cout << ch; else if (' ' == ch) // ch == ' '错误防范,防止将==写成= std::cout << ch; else std::cout << char(ch + 1); std::cin.get(ch); } std::cout << "\nPlease excuse the slight confusion.\n"; std::cin.get(); std::cin.get(); return 0; } ``` ## 6.2 逻辑表达式 逻辑OR(||, or)、逻辑AND(&&, and)、逻辑NOT(!, not)。 🐸注意: 1. 不要误将`age > 17 && age < 35`写成`17 < age < 35`,这样程序不会报错,但测试结果将一直为true; 2. 逻辑运算符`||, &&`的优先级比关系运算符低,但`!`的优先级高于所有的关系运算符和算术运算符,`&&`高于`||`; 3. C++确保程序从左向右进行计算逻辑表达式,并在知道答案后立刻停止; ## 6.3 字符函数库cctype 简化一些有关字符的工作。 这些函数的返回类型为int,但在测试表达式时,会激活bool转换。 `isalpha(ch)`:是否为字母; `ispunct(ch)`:是否为标点符号; `isdigits(ch)`:是否为数字(0~9); `isspace(ch)`:是否为空白(换行、空格、制表符); `isalnum(ch)`:是否为字母数字; `iscntr(ch)`:是否为控制字符; `isgraph(ch)`:是否为除空格之外的打印字符; `islower(ch)`:是否为小写字母; `isprint(ch)`:是否为打印字符(包括空格); `isupper(ch)`:是否为大写字母; `isxdigit(ch)`:是否为十六进制数字; `tolower(ch)`:大写变小写; `toupper(ch)`:小写变大写; ## 6.4 ?:运算符 常被用来代替if else语句,是C++中唯一一个需要3个操作数的运算符。 `expression1 ? expression2 : expression3` 如果expression1为true,则整个表达式的值为expression2的值,否则为expression3的值,条件表达式可以嵌套在另一个条件表达式中。 ```cpp const cgar x[2][20] = {"Jason ", "at your service\n"}; const char * y = "Quillstone "; for (int i = 0; i < 3; i++) cout << ((i < 2) ? !i ? x[i] : y : x[1]); // Jason Quillstone at your service ``` ## 6.5 switch语句 🌈注意哦: 1. case只是行标签,程序跳到switch中特定代码行后,将以此执行之后的所有语句,除非有明确的其他指示(如break); 2. 更容易从大型列表中进行选择,如果既可以使用if else if语句,又可以使用switch语句,则当选项不少于3个时,应使用switch语句,效率更高; 3. 如果default标签被省略,而又没有匹配的标签,则程序将跳到switch后面的语句处执行。 ```cpp switch (integer-expression) { case label1 : statement(s) case label2 : statement(s) ... default : statement(s) } ``` ## 6.6 break和continue语句 一个跳出本层,一个跳出本次。 ## 6.7 简单文件输入/输出 ```cpp #include #include using namespace std; /********** 输出 **********/ ofstream outFile; ofstream fout; // outFile可使用cout可使用的任何方法 outFile.open("fish.txt"); char filename[50]; cin >> filename; fout.open(filename); double wt = 125.8; outFile << wt; char line[20] = "I love coding."; fout << line << endl; /********** 输入 **********/ ifstream inFile; ifstream fin; inFile.open("bowling.txt"); // 检查文件是否被成功打开 #include if (!inFile.is_open()) { exit(EXIT_FAILURE); } // 检查是否为EOF inFile.good(); inFile.eof(); char filename[50]; cin >> filename; fin.open(filename); double wt; inFile >> wt; // 从文件中读取一个数字 char line[81]; fin.getline(line, 81); // 从文本中读取一行 ... inFile.close(); ``` - 阅读全文 -
CPP 5. 循环和关系表达式 2020-10-16 浏览量 604 暂无评论 # 第5章 循环和关系表达式 ## 5.1 for循环 注意: 1. test-expression(测试表达式)可以使用任意表达式,不是只能为真或假,C++将结果强制转换为bool类型; 2. for循环是入口条件循环,意味着在每轮循环之前,都将计算测试表达式的值; 3. for语句的控制部分使用3个表达式,而在C++中,任何值或任何有效的值和运算符的组合都是表达式(表达式加分号成为语句,但语句去掉分号不一定是表达式),C++将赋值表达式的值定义为左侧成员的值; 4. 完整表达式--表达式语句中的表达式部分,while循环中检测条件的表达式。任何完整的表达式末尾都是一个顺序(点),C++确保执行下一跳语句前,完整表达式的所有操作被执行完毕; 5. 逗号表达式的值是最后一部分的值,逗号运算符的优先级是最低的; 6. 循环体必须是一条语句,该语句可以是**复合语句或者用逗号运算符合并**出的语句,使用像python那样的缩进是不行的,那样只有第一条语句被当作循环体; 7. 复合语句中定义的变量只在该复合语句内有效; **for循环语法** ```cpp #include int main() { using namespace std; int i; // init; test; update for (i = 0; i < 5; i++) // for (int i = 0; i < 5; i++) cout << "You are so smart!\n"; cout << "Stop.\n"; return 0; } // 不完整的表达式,下式中的4 + x++ y = (4 + x++) + (6 + x++); // 逗号运算符 a = (i = 20, j = 2 * i); // a = 40, i = 20, j = 40 a = i = 20, j = 2 * i; // a = 20, i = 20, j = 40 ``` **复合语句,逗号运算符合并语句** ```cpp #include #include int main() { using namespace std; cout << "Enter a word: " string word; cin >> word; chat temp; int i, j; for (j = 0, i = word.size() - 1; j < i; --i, ++j) { temp = word[i]; word[i] = word[j]; word[j] = temp; } cout << word << "\nDone\n"; return 0; } ``` ## 5.2 while循环 注意: 1. while循环是一种入口条件循环; **while循环实现延时** ```cpp #include #include int main() { using namespace std; cout << "Enter the delay time, in seconds: "; float secs; cin >> secs; clock_t delay = secs * CLOCKS_PER_SEC; cout << "staring\a\n"; clock_t start = clock(); while (clock() - start < delay) ; cout << "done \a\n"; cin.get(); cin.get(); return 0; } ``` **类型别名** ```cpp // 使用预处理器 #define BYTE char // 使用关键字typedef(推荐,可用于声明一系列变量) typedef char byte; typedef char * byte_pointer; // pointer to char type ``` ## 5.3 do while循环 注意: 1. do while循环是一种出口条件循环; 2. 适合必须先获得输入才能进行测试的情况; ```cpp int n; do { cin >> n; } while (n != 7); ``` ## 5.4 基于范围的for循环(C++11) ```cpp double prices[5] = {4.99, 10.99, 2.32}; for (double x : prices) cout << x << std::endl; // &表明x是一个引用变量,这种声明让接下来的代码能够修改数组的内容 for (double &x : prices) x = x * 0.80; ``` ## 5.5 循环和文本输入 注意: 1. 原始cin忽略空格和换行符; 2. cin.get(char)可以读取空格; ```cpp // 使用原始cin,忽略空格和换行符 char ch; int count = 0; cout << "Enter characters; enter # to quit:\n"; cin.get(ch); while (ch != '#') { cout << ch; ++count; cin.get(ch); } cout << endl << count << "characters read\n"; return 0; // cin.fail()检测文件尾条件(EOF) // while(!cin.fail()) <=> while(cin) <=> while(cin.get(ch)) // cin.get(char)的返回值为cin // 下面两程序等价 char ch; cin.get(ch); // EOF可能与char类型不兼容 while (cin.fail() == false) { cout << ch; ++count; cin.get(ch); } int ch; ch = cin.get(); while (ch != EOF) { cout.put(ch); ++count; ch = cin.get(); } int ch; while((ch = cin.get()) != EOF) { cout.put(char(ch)); ++count; } ``` 小知识点: 如果留心,会发现cin.get()有好多种,比如`cin.get(); cin.get(ch); cin.get(name, ArSize);`为什么名字一样,但是可以传入不同的参数呢?这是因为函数重载,它允许创建多个同名函数,条件是它们的参数列表不同。 ## 5.6 嵌套循环和二维数组 C++没有提供二维数组类型,但用户可以创建每个元素本身都是数组的数组 **二维数组** ```cpp int btus[5] = {23, 26, 24, 31, 28}; int maxtemps[4][5] = { {96, 100, 87, 101, 105}, {2, 3, 4, 5, 7}, {4, 4, 6, 7, 9}, {45, 345, 234, 21, 321} }; const char * cities[3] = { "Nanjing", "Beijing", "lianyungang" }; ``` - 阅读全文 -