CPP 7. 函数--C++的编程模块 2020-10-17 浏览量 456 暂无评论 # 第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 浏览量 539 暂无评论 # 第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 浏览量 608 暂无评论 # 第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" }; ``` - 阅读全文 -
CPP 4. 复合类型 2020-10-15 浏览量 590 暂无评论 # 第4章 复合类型 最深远的复合类型是类。 ## 4.1 数组 ### 4.1.1 声明方法 ```cpp typeName arrayName[arraySize]; short months[12]; //从0开始编号 ``` 注意: 1. 编译器不会检查使用的下标是否有效,当下标超出数组范围时,编译器并不会报错,但是程序运行后,会引发问题,也可能导致程序异常终止; 2. 存储多个同类型的值; 3. 在内存中是连续的。 ### 4.1.2 初始化规则 1. 只有在定义数组时才能使用初始化,不能先声明再直接对整个数组赋值,但是可以使用下标分别给数组中的元素赋值; ```cpp int card[4] = {3, 6, 8, 10}; // okay int hand[4]; // okay hand[4] = {5, 6, 7, 9}; // not allowed hand = cards; // not allowed hand[0] = 3; // okay ``` 2. 初始化数组时,提供的值可以少于数组的元素数目,剩余的会被编译器自动设置为0,这在将数组中所有元素都初始化为0时很有用; 3. 如果初始化数组时方括号内([])为空,C++编译器将会自动统计元素个数(在初始化数组时通常别这样做,但初始化字符串时这样很方便); 4. 初始化数组时,可省略等号(=)(C++11); 5. 初始化数组时,可不在大括号内包含任何东西,默认为0(C++11); 6. 初始化时禁止缩窄转换(C+11)。 **ps:** C++标准模板库提供了数组的替代品--模板类`vector`,C++11新增了模板类`array`,比内置复合类型数组更复杂、更灵活。 具体怎么灵活呢? ## 4.2 字符串 ### 4.2.1 字符串初始化 注意: 1. 字符串在内存中也是连续的,可以存储在char数组中; 2. C-风格字符串以空字符(null character)结尾,被写作\0,其ASCII码为0,用来标记字符串的结尾,不是空格; 3. 使用引号括起的字符串常量,隐式地包括结尾的空字符; 4. `''`和`""`的区别很大,一个是字符常量,一个是字符串常量,如`"S"`表示两个字符(字符S和\0)组成的字符串。 ```cpp char dog[8] = {'b', 'e', 'a', 'u', 'x', ' ', 'I', 'I'}; // not a string char cat[8] = {'f', 'a', 't', 't', 'e', 's', 's', 'a', '\0'}; // a string char fish[] = "Bubbles"; // a string char shirt_size = "S"; // illegal type mismatch ``` ### 4.2.2 字符串拼接 😄任何两个由空白(空格、制表符、换行符)分隔的字符串常量都将自动拼接成一个,第一个字符串的\0字符将被第二个字符串的第一个字符取代。 ### 4.2.3 在数组中使用字符串 **string.cpp** ```cpp #include #include // for strlen() function int main() { using namespace std; const int Size = 15; char p1[Size]; char p2[Size] = "却又吹得让它慌张\n"; cout << "你问风为什么托着候鸟飞翔, " << p2; cin >> p1; cout << "却也湿透她的衣裳\n"; cout << "你问我为什么还是不敢放下, " "明知听不到回答\n"; } ``` 注意: 1. `strlen()`函数返回存储在数组中的字符串的长度,只计算可见的字符,不包含空字符; 2. `sizeof()`运算符统计整个数组的长度。 ### 4.2.4 字符串的输入 **insre1.cpp** ```cpp #include int main() { using namespace std; const int ArSize = 20; char name[ArSize]; char dessert[ArSize]; cout << "Enter your name:\n"; cin >> name; cout << "Enter your favorite dessert:\n"; cin >> dessert; cout << "I have some delicious " << dessert; cout << " for you, " << name << ".\n"; return 0; } ``` >Enter your name: >**Jineng Dreeb** >Enter your favorite dessert: >I have some delicious Dreeb for you, Jineng. 在本例中,都没有对“Enter your favorite dessert:”作出反应,程序就已经显示出来了,为什么呢?这关系到`cin`如何确定已完成对字符串的输入。 由于不能通过键盘输入空字符,因此`cin`使用空白(空格、制表符、换行符)来确定字符串的结束位置,`cin`在获取字符数组时只读取一个单词,然后将该字符串放在数组中,并自动在结尾添加空字符,后面的单词留在输入队列中,等待下一次读取。 ### 4.2.5 每次读取一行字符串输入 C++常使用指针来处理字符串。 注意: 1. `cin.getline()`每次读取一行,读到换行符,随后丢弃换行符,保存时将换行符替换为空字符; 2. `cin.get()`读到换行符,将换行符留在队列中; 3. `cin.get()`用来检查错误更方便,通过读取下一个字符是不是换行符可以知道之前读的字符串是否超出了数组的长度; 4. *`cin.get()`* 在读取空行后将设置失效位(failbit),接下来的输入将会被阻断(**直接回车,程序直接退出了**),但可用`cin.clear()`来恢复输入; 5. 当输入的字符串比分配空间长时,都会把余下的字符留在输入队列中,而 *`cin.getline()`* 还会设置失效位,并关闭后面的输入(**直接回车,程序直接退出了**)。 以下语句等价: ```cpp cin.getline(name1, ArSize).getline(name2, ArSize); cin.get(name1, ArSize).get().getline(name2, ArSize).get(); // cin.get()读取下一个字符(即使是换行符) ``` ##### 4.2.6 混合输入字符串和数字 如下程序会出现问题,用户没有输入地址的机会。因为cin读取年份后,将回车键生成的换行符留在了输入队列,getline会把换行符当作空行赋给address数组,可以在cin后加get。 ```cpp int year; cin >> year; char address[80]; cin.getline(address, 80); ``` ## 4.3 String类 **特点:** 1. 自动调整长度; 2. 使字符串操作更容易。 ### 4.3.1 初始化 ```cpp string str1; cin >> str1; // str1开始长度为0,但将输入读取到str1中时,将自动调整str1长度 string str2 = "panther"; // C++11 char first[] = {"JHAN"}; char second[] {"JHAN"}; string third = {"JHAN"}; string fourth {"JHAN"}; ``` ### 4.3.2 赋值、拼接和附加 不能将一个数组赋给另一个数组,但是可以将一个string对象赋给另一个string对象。 ```cpp string str3; str3 = str1 + str2; // 拼接str1和str2给str3,cstring strcpy (这个没法自动调整长度,可用stmcpy接受第三个参数) str1 += str2; // 把str2加到str1后面,cstring strcat (这个没法自动调整长度,可用stmcat接受第三个参数) string str; getline(cin, str); ``` ##### 4.3.3 原始字符串(C++11) ```cpp cout << R"(jhan "jhan" "\n")" << '\n'; cout << R"+*("(jhan)", jhan)+*" << '\n'; ``` >jhan "jhan" "\n" >"(jhan)", jhan ## 4.4 结构简介 **特点:** 1. 存储多种类型的数据; 2. 使用运算符(.)来访问各个成员; 3. 外部声明(位于函数外面)和内部声明(位于main()函数内部)的作用域,若内部声明和外部声明名称重复,则在函数内部以内部声明为准; 4. 对结构的操作与内置类型相似(例如作为参数传递,作为返回值,成员赋值)。 ```cpp // 结构描述: struct inflatable { char name[20]; float volume; double price; }; // 声明结构变量 struct inflatable goose; // C需要使用关键字struct inflatable vincent; // C++可以省略关键字struct // 初始化结构变量 inflatable guest = { "jhan", 1.88, 29.99 }; // 有些实现需要用 static inflatable guest,为什么呢? inflatable duck {"yuli", 0.12, 9.98}; inflatable duck {}; // 都被设置为零 // 可以同时完成定义结构和创建结构变量的工作 struct perks { int key_number; char car[12]; } mr_smith, ms_jones; // 甚至可以同时初始化 struct perks { int key_number; char car[12]; } mr_glitz = { 7, "jhan" }; // 还可以声明没有名称的结构类型,但需要同时定义变量 struct { int x; int y; } position; /******** 结构数组 ********/ inflatable gifts[100]; // array of 100 inflatable structures inflatable guests[2] = { {"Bambi", 0.5, 21.99}, {"Godzilla", 2000, 565.99} }; // 位字段,指定占用特定位数的结构成员(使得创建与某个硬件设备上的寄存器对应的数据结构特别方便) // 字段的类型应为整型或枚举 struct torgle_register { unsigned int SN : 4; // 4 bits for SN value unsigned int : 4; // 4 bits unused bool goodIn : 1; bool goodTorgle : 1; }; torgle_register tr = {14, true, false}; ``` ## 4.5 共用体 **特点:** 1. 能够存储不同类型的数据类型,但只能同时存储其中一种类型; 2. 共用体的长度为其最大成员的长度(那还怎么节省空间?) ```cpp union one4all { int int_val; long long_val; double double_val; }; struct widgrt { char brand[20]; int type; union id { long id_num; char id_char[20]; } id_var; }; ... widget prize; ... if (prize.type == 1) cin >> prize.id_val.id_num; else cin >> prize.id_var.id_char; // 匿名共用体,共用体成员被视为变量prize的成员,它们的地址相同,不需要中间标识符id_val struct widgrt { char brand[20]; int type; union { long id_num; char id_char[20]; }; }; ... widget prize; ... if (prize.type == 1) cin >> prize.id_num; else cin >> prize.id_char; // 可以使用one4all变量来存储int、long或double,条件是在不同的时间进行 one4all pail; pail.int_val = 15; pail.double_val = 1.38; ``` **作用(使用场景):** - [ ] 疑问: 到底怎么节省的?从下面的程序运行结果来看,所占空间并没有随使用的变量数据类型而动态变化啊。常用于节省内存。当数据项使用两种或更多种格式(但不会同时使用)时,可节省空间。 ```cpp #include union test { int a; double b; }; int main() { using namespace std; test jhan; jhan.a = 4; cout << "size of int is " << sizeof(int) << endl; // 4 cout << "size of double is " << sizeof(double) << endl; // 8 cout << "size of jhan is " << sizeof(jhan) << endl; // 8 cout << "size of test is " << sizeof(test) << endl; // 8 jhan.b = 8.23; cout << "size of jhan is " << sizeof(jhan) << endl; // 8 cout << "size of test is " << sizeof(test) << endl; // 8 cin.get(); } ``` ## 4.6 枚举 **作用:** 1. 创建符号常量,代替const; 2. 枚举量是整型,可被提升为int类型,但int类型不能自动转换为枚举类型; 3. 在算术表达式中,枚举将被转换为整数,得到的类型为int,不能直接赋给枚举变量 ```cpp // 让spectrum成为新类型的名称;spectrum被成为枚举(enumeration); // 将red、orange、yellow等作为符号常量,对应整数值0~7,这些常量叫作枚举量 enum spectrum {red, orange, yellow, green, blue, violet, indigo}; enum {red, orange, yellow, green, blue, violet, indigo}; // 一些限制 spectrum band; band = blue; // valid band = 2000; // invalid band = orange + red; // invalid int color = blue; // valid, promoted to int band = 3; // invalid band = spectrum(3); // valid band = spectrum(4003); // undefined, result not sure color = 3 + red; // valid // 设置枚举量的值 enum bits{one = 1, two = 2, four = 4, eight = 8}; bits myflag; myflag = bits(6); // valid // first = 0, third = 101 enum bigstep{first, second = 100, third}; // zero, null = 0; one, numero_uno = 1 enum {zero, null = 0, one, numero_uno = 1}; // 枚举的取值范围,允许在取值范围内使用强制类型转换将整数值给枚举变量 上限:大于最大值的最小的2的幂减1 下限:若最小值不小于0,则下限为0;否则。小于最小值的最大的2的幂(加负号)加1 ``` ## 4.7 指针和自由空间 计算机程序在存储数据时必须跟踪的3种基本属性: 1. 信息存储在何处; 2. 存储的值为多少; 3. 存储的信息是什么类型。 达到这种目的的一种策略:定义简单变量,指出值得类型和符号名,让程序为值分配内存,并在内部跟踪该内存单元,**这时值是指定的量,而地址为派生量**。 **为什么要使用指针?** 使程序能够在运行阶段动态分配内存(配合new),节省空间,而不是在编译阶段就将所需的空间按可能的最大值确定下来。**这时地址为指定的量,而值为派生量**。 **特点:** 1. 改变指针所指的地址处的值,此处的变量值也变; 2. 尽管指针有许多不同的类型,但是所有指针变量本身的长度都是一样的,只是它们指向的数据类型不同; 3. 指针名可以用来加减,实现地址偏移(一次偏移一个类型长度); **注意点:** 1. 一定要在对指针应用解除引用运算符(*)之前,将指针初始化为一个确定的、适当的地址,错误示范:`long * fellow; *fellow = 223323;`地址不明确,可能导致一些**最隐匿、最难以跟踪**的bug; 2. 地址本身只指出了对象存储地址的开始; 3. 内存耗尽,new引发异常; 4. new创建数组时,返回第一个元素的地址给指针; 5. 只有在使用了new后,才能使用delete,将它用于new分配的内存; 6. 不能使用sizeof运算符来确定动态分配的数组包含的字节数,但是程序确实跟踪了分配的内存量(只是信息不公用) ```cpp // 指针与地址 #include int main() { using namespace std; int a = 6; // *运算符两边的空格是可选的 // C: int *ptr; // C++: int* ptr; int * p_a; // 下面的声明创建一个指针(p1)和一个int变量(p2),对每个指针变量名,都需要使用一个* int* p1, p2 p_a = &a; /* a == *p_a &a == p_a */ *p_a = *p_a + 1; // a = 7 // 指针和数字 int * pt; pt = 0XB8000000; // type mismatch pt = (int *) 0XB8000000; // type now match // 使用new来分配内存(在运行阶段分配未命名的内存以存储值) // 常规变量声明从栈(stack)分配内存,new从堆(heap)或自由存储区(free store)分配内存 int * pn = new int; *pn = 2020; // 使用delete释放内存 int* ps = new int; int* pq = ps; cout << "ps: " << ps << endl; cout << "pq: " << pq << endl; // pd == ps delete pq; /* 如果程序只需要小型数据对象,那直接声明简单变量比使用new和指针更简单, 对于大型数据,应使用new。 在编译时为数组分配内存空间称为静态联编(static binding), 在运行时选择创建并选择长度称为动态联编(dynamic binding)。 */ // 使用new创建动态数组 // new返回第一个元素的地址,该地址被赋给指针psome int * psome = new int [10]; // 还不是占了10个int??? delete [] psome; // 指针名可以用来加减,地址偏移 double * p3 = new double [3]; p3[0] = 0.2; p3[1] = 0.5; p3[2] = 0.8; p3 = p3 + 1; // p3[0] = 0.5 p3 = p3 - 1; // p3[0] = 0.2 } ``` ## 4.8 指针、数组、指针算术 从面的例子中可以看出,我们可以像操作数组一样操作指针,原因在于指针算术和C++内部处理数组的方式。 **知识点:** 1. 整数变量加1后,其值将增加1;将指针变量加1后,增加的量等于它指向的类型的字节数; 2. C++将数组名解释为地址; 3. 对数组应用sizeof得到的是数组的长度,对指针应用sizeof得到的是指针的长度; 4. `&数组名`得到的是整个数组的地址,虽然cout输出与`数组名`的结果相同,但是它们加1后的结果不一样; 5. 数组指针:假设数组长度为20,指向数组的指针`double (*pas)[20] = &arrayName`,使用`double *pas = &arrayName`会报错;指针数组`double *pas[20]`,数组的元素是指针; 6. 在cout和多数C++表达式中,char数组名、char指针以及用引号括起的字符串常量都被解释为字符串第一个字符的地址; 7. 应使用strcpy()或strncpy()将字符串赋给数组,而不是赋值运算符; 8. 不能使用`=`将一个字符串赋给另一个字符数组或字符指针; **数组与指针** ```cpp #include int main() { using namespace std; double wages[3] = {100.0, 200.0, 300.0}; short stacks[3] = {3, 2, 1}; // 两种获取数组地址的方式 double * pw = wages; short * ps = &stacks[0]; // 这也说明数组的地址是第一个元素的起始地址 cout << "wages: " << wages << endl; cout << "&wages: " << &wages << endl; // 数组的地址,double (*pas)[20] = &tell cout << "wages + 1: " << wages + 1 << endl; cout << "&wages + 1: " << &wages + 1 << endl; cout << "wages[0]: " << wages[0] << endl; cout << "*wages: " << *wages << endl; cout << "*(&(wages[0])): " << *(&(wages[0])) << endl; cin.get(); /* wages: 0x70fdf0 &wages: 0x70fdf0 wages + 1: 0x70fdf8 &wages + 1: 0x70fe08 wages[0]: 100 *wages: 100 *(&(wages[0])): 100 */ } ``` **使用new创建动态结构** 创建动态结构时,不能将成员运算符句点用于结构名,因为这种结构没有名称,只是知道它的地址。这种情况有个专门的运算符`->`。 ```cpp struc things { int good; int bad; }; things grubnose = {3, 453}; things *pt = &grubnose; // grubnose.good == pt->good == (*pt).good ``` **一个使用new和delete节省内存的示例** ```cpp #include #include using namespace std; char * getname(void); int main() { char *name; name = getname(); cout << name << " at " << (int *) name << endl; delete [] name; name = getname(); cout << name << " at " << (int *) name << endl; delete [] name; cin.get(); cin.get(); return 0; } char * getname() { char temp[80]; cout << "Enter last name: "; cin >> temp; char * pn = new char[strlen(temp) + 1]; strcpy(pn, temp); return pn; } ``` **自动存储、静态存储、动态存储** C++的3中管理数据内存的方式,C++11新增了第四种类型:线程存储。 1. **自动存储** **生命周期:** 在函数内部定义的常规变量使用自动存储空间,被称为自动变量,意味着它在所属的函数被调用时自动产生,该函数结束时消亡。实际上,自动变量是一个局部变量,其作用域为包含它的代码块。 **存储位置:** 通常存储在栈中,后进先出(LIFO)。 2. **静态存储** **生命周期:** 整个程序执行期间都存在。使变量成为静态的两种方式:在函数外定义它;在声明时使用关键字static。 3. **动态存储** 使用new和delete实现。它们管理了一个内存池,这在C++中被称为自由存储空间(free store)或堆(heap),该内存池与用于静态变量和自动变量的内存是分开的。 注意:要养成同时使用new和delete的习惯,否则容易发生内存泄漏,**C++智能指针**有助于自动完成这种任务。 **指向指针的指针** - [ ] 为什么跟结构有关的指针前面要加关键字const呢? ```cpp struct person { int age; }; person p1, p2, p3; const person * aim[3] = {&p1, &p2, &p3}; const person ** ppa = aim; auto ppb = aim; ``` ## 4.9 数组的替代品:模板类vector和array **模板类vector** `vector vt(n_elem);` 类似于string类,是一种动态数组。 ```cpp #include ... using namespace std; vector vi; // create a zero-size array of int int n; cin >> n; vector vd(n); // create an array of n doubles ``` **模板类array(C++11)** `array arr;` 注意: 1. 只有在C++11中,才可以将列表初始化用于vector和array对象,其他情况只能一个个赋值; 2. array对象和数组存储在相同的内存区域(栈),vector存储在另一区域(自由存储区或堆); 3. 不允许使用负索引,但使用中括号时C++不会检查,可以使用at成员函数来捕获非法索引。 - 阅读全文 -
CPP 3. 处理数据 2020-10-13 浏览量 604 暂无评论 # 第3章 处理数据 ## 3.1 强制类型转换 ```cpp // typecast.cpp -- forcing type changes #include int main() { using namespace std; int auks, bats, coots; auks = 19.99 + 11.99; bats = (int) 19.99 + (int) 11.99; // old C syntax coots = int (19.99) + int (11.99); // new C++ syntax cout << "auks = " << auks << ", bats = " << bats; cout << ", coots = " << coots << endl; ``` ## 2 auto声明(C++11) 让编译器能够根据初始值的类型推断变量的类型。 并不是为初始化声明而生的(这根本没必要),反而会让人误入歧途,只有处理复杂类型,如标准模块库(STL)中的类型时,自动推断类型的优势才能显现出来,例如: ```cpp // C++98 std::vector scores; std::vector::iterator pv = scores.begin(); // C++11 std::vector scores; auto pv = scores.begin(); ``` - 阅读全文 -