1.第4章 复合类型
4.2字符串
4.2.2在数组中使用字符串
- strlen()函数返回的是存储在数组中的字符串长度,而不是数组本身的长度。另外,strlen()只计算可见的字符,而不把空字符计算在内。例如:int name1[15] = "Basicman";则strlen(name1) = 8。
4.2.3字符串输入
#include <iostream>
using namespace std;
int main(void)
{
const int Arsize = 20;
char name[Arsize];
char desert[Arsize];
cout << "Enter your name:\n";
cin >> name;
cout << "Enter your favorite desert:\n";
cin >> desert;
cout << "I have some delicious " << desert;
cout << " for you." << name << endl;
system("pause");
return 0;
}
cin使用空白(空格、制表符、换行符)来确定字符串的结束位置。
4.2.4每次读取一行字符串输入
getline()和get()都读取一行输入,直到达到换行符。随后getline()将丢弃换行符,而get将换行符保留在输入序列中。
1.面向行的输入getline()
语法:cin.getline(数组名称,字符数);
例如,假设要使用getline()将姓名读入到一个包含20个元素的name数组中,cin.getline(name,20);字符数为20,则函数最多读取19个字符,余下的空间用于存储自动在结尾处添加的空字符。
#include <iostream>
using namespace std;
int main(void)
{
const int Arsize = 20;
char name[Arsize];
char desert[Arsize];
cout << "Enter your name:\n";
//cin >> name;
cin.getline(name, Arsize);
cout << "Enter your favorite desert:\n";
//cin >> desert;
cin.getline(desert, Arsize);
cout << "I have some delicious " << desert;
cout << " for you." << name << endl;
system("pause");
return 0;
}
getline()函数每次读取一行,它通过换行符来确定行尾,但不保存换行符。相反,在存储字符串时,用空字符来替换换行符。
2.面向行的输入:get()
get()不丢弃换行符,将其留在输入队列中。假设调用两次get():
cin.get(name,Arsize);
cin.get(desert,Arsize); //a problem
第一次调用后,换行符留在输入队列中,第二次调用看到的第一个字符便是换行符。因此get()认为已达行尾,而没有发现任何可读取的内容。
解决方式:
1.cin.get(name,Arsize);
cin.get();
cin.get(desert,Arsize);
2.cin.get(name,Arsize).get();
cin.get(desert,Arsize).get();
3.空行及其他问题
get()读取空行后将设置失效位,接下来的输入将被阻断,但可以使用cin.clear();恢复输入。
4.混合输入字符串和数字
#include <iostream>
using namespace std;
int main(void)
{
cout << "What year was your house built?" << endl;
int year;
cin >> year;
cout << "what is its street address?" << endl;
char address[80];
cin.getline(address, 80);
cout << "Year built:" << year << endl;
cout << "address:" << address << endl;
cout << "Done" << endl;
system("pause");
return 0;
}
解决方法:在读取地址之前先读取并丢弃换行符。
1.cin >>year;
cin.get(); //or cin.get(ch);
2.(cin>>year).get(); //or (cin>>year).get(ch);
4.3string类简介
4.3.2赋值、拼接和附加
1.不能将一个数组赋给另一个数组,但可以将一个string对象 赋给另一个string对象。
例如:char charr1[20];char charr2[20] = "jaguar"; charr1 = charr2; //×
string str1; string str2 = "panther"; str1 = str2; //√
2.string类简化了字符串合并操作。可以用运算符+将两个string对象合并起来,还可以使用运算符+=将字符串附加到string对象的末尾。
4.5共用体(P94、P95)
能够存储不同的数据类型,但只能同时存储一种类型。共用体常用来节省内存。
4.6枚举
1.设置枚举变量的值
- 可以使用赋值运算符显式地设置枚举变量的值。enum bits{one = 1,two = 2,four = 4,eight = 8};
- 指定的值必须是整数。也可以只显式地定义其中一些变量的值。enum big{fist,second =100,third};first默认为0,后面没有初始化的枚举变量的值比前面的枚举变量大1。因此third值为101。
- 可以创建多个值相同的枚举量。enum big{zero,null = 0,one,numero_nuo = 1};其中,zero和null都为0,one和numero_nuo都为1。
2.枚举的取值范围
上限:找到枚举量的最大值,找到大于最大值的最小的2的幂,将它减去1,得到上限。如,最大枚举值是101,则在2的幂中,比这个数大的最小值为128,因此取值范围上限为127。
下限:找到枚举量的最小值。如果它大于等于0,下限为0。如果是负数,与寻找上限方式相同,但最后加上负号。例如,最小枚举量为-6,而比它小的、最大的2的幂是-8,因此下限为-7。
4.7指针和自由存储空间
4.7.4使用new来分配内存
- typeName *pointer_name = new typeName;例如int *pn = new int;需要在两个地方指定数据类型:用来指定需要什么样的内存和用来声明合适的指针。
- 常规变量声明分配内存块:int higgens;int *pt = &higgens; new分配内存块:int *pn = new int;new从堆区中分配内存。
4.7.5使用delete释放内存
1.int *ps = new int; //allocate memory with new
......... //use the memory
delete ps; //free memory
2.释放ps指向的内存,但不会删除ps指针本身,例如,可以将ps重新指向另一个新分配的内存块。
内存泄漏:不配对使用new和delete,一直new而不释放,被分配的内存再也无法使用。
3.不能释放已经释放的内存。
4.不能使用delete释放声明变量所获得的内存,只能用来释放new分配的内存。
4.7.6使用new来创建动态数组
静态联编:在编译时给数组分配内存。
动态联编:在运行阶段创建需要的数组并选择长度。动态数组。
1.使用new来创建动态数组
int *psome = new int [10]; //new元素返回第一个元素的地址,该地址被赋值给指针psome。
delete [] psome; //方括号告诉元素释放整个数组,而不仅仅是指针指向的元素。
2.使用new和delete应遵守的规则
- 不要使用delete来释放不是new分配的内存。
- 不要使用delete释放同一个内存块两次。
- 如果使用new[]为数组分配内存,则应使用delete [] 来释放。
- 对控制指针应用delete 是安全的。
3.使用动态数组
访问动态数组:将指针当作数组名即可。int *psome = new int [10];则第一个元素psome[0],第二个元素psome[1]..........
4.8指针、数组和指针算术
1.将整数变量加1后,其值将增加1。但将指针变量加1后,增加的量等于它所指向类型的字节数。
2.arrayname[i] = *(arrayname + i) pointername[i] = *(pointername + i)
3.数组名和指针名区别:
(1)可以修改指针的值,而数组名是常量。
pointername = pointername+1; //vaild
arrayname = arrayname+1; //not allowed
(2)对数组应用sizeof()运算符得到的是数组的长度,而对指针应用sizeof()得到的是指针的长度。
4.8.4使用new来创建动态结构
1.创建结构: inflatable *ps = new inflatable; 将足以存储inflatable结构的一块可用内存的地址赋给ps。
2.访问结构成员:如果标识符是结构名,则使用句点运算符。如果标志符是指向结构的指针,则使用箭头运算符。还有一种方法是,如果ps是指向结构的指针,则*ps就是被指向的值——结构本身,因此(*ps).price是该结构的price成员。
3.一个使用new和delete的示例
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
char* getname(void)
{
char temp[80];
cout << "Enter the last name:";
cin >> temp;
char* pn = new char[strlen(temp) +1];
strcpy(pn, temp); //copy string into smaller space
return pn;
}
int main(void)
{
char* name;
name = getname();
cout << name << " at " << (int*)name << endl;
delete[] name;
name = getname();
cout << name << " at " << (int*)name << endl;
delete[] name;
system("pause");
return 0;
}
getname()函数返回一个指向输入字符串的指针。该函数将输入读入到一个大型的临时数组中,然后使用new创建一个刚好嫩刚好能存储该输入字符串的内存块,并返回一个指向该内存块的指针。(节省内存)
4.8.5自动存储、静态存储和动态存储
c++3种管理数据内存的方式:自动存储、静态存储、动态存储。
1.自动存储:局部变量 在所属函数被调用时自动产生,在该函数结束时消亡 通常存储在栈中 后进先出 在程序执行过程中栈不断地增大和缩小
2.静态存储:整个程序执行期间都存在 静态变量有两种:一种是在函数外面定义它,另一种是在声明变量时使用关键字stattic。
3.动态存储: 堆区
4.10数组的替代品
4.10.1模板类vector
模板类vector 类似于string类,也是一种动态数组。基本上,他是使用new创建动态数组的替代品。
- 必须包含头文件 #include
- 语法: vector vt; n_elem可以是整型常量,也可以是整型变量。例如,vector vd;
4.10.2模板类array
- 头文件 #include
- 语法:array arr; n_elem不能是变量,只能是常量。
4.10.3比较数组、vector对象和array对象
#include <iostream>
#include <vector>
#include <array>
using namespace std;
int main(void)
{
//数组
double a1[4] = { 1.2,2.4,3.6,4.8 };
//vector对象
vector<double> a2(4);
a2[0] = 1.0 / 3.0;
a2[1] = 1.0 / 5.0;
a2[2] = 1.0 / 7.0;
a2[3] = 1.0 / 9.0;
//array对象
array<double, 4> a3 = { 3.14,2.72,1.62,1.41 };
array<double, 4>a4;
a4 = a3;
cout << "a1[2]:" << a1[2] << " at " << &a1[2] << endl;
cout << "a2[2]:" << a1[2] << " at " << &a2[2] << endl;
cout << "a3[2]:" << a1[2] << " at " << &a3[2] << endl;
cout << "a4[2]:" << a1[2] << " at " << &a4[2] << endl;
a1[-2] = 20.2;
cout << "a1[-2]:" << a1[-2] << " at " << &a1[-2] << endl;
cout << "a3[2]:" << a3[2] << " at " << &a3[2] << endl;
cout << "a4[2]:" << a4[2] << " at " << &a4[2] << endl;
system("pause");
return 0;
}
总结:
- 无论是数组、vector对象还是array对象,都可以使用标准数组表示法来访问各个元素。
- 从地址可以看出,array对象和数组存储在相同的内存区域(即栈)中,而vector对象存储在另一个区域(堆)中。
- 可以将一个array对象赋值给另一个array对象;而对于数组,必须逐元素复制数据。
- a1[-2] = 20.2;//a1[-2] = *(a1-2) 非法是不安全的代码。避免方式:使用成员函数at()。使at时,将在运行期间捕获非法索引。a2.at(1) = 2.3; //assign 2.3 to a2[1]
2.第五章 循环和关系表达式
5.1for循环
1.通常,cout在显示bool值前将他们转化为int,但cout.setf(ios::boolalpha)函数调用设置了一个标记,该标记命令cout显示true和false,而不是1和0.(p129)
5.1.4C风格字符串的比较(p142-144)
假设要知道字符数组中的字符串是不是mate。如果word是数组名:word=="mate"; ❌ 因为数组名是地址,用引号括起的字符串常量也是其地址。因此这个关系表达式不能判断两个字符串是否相同,而是在查看他们是否存储在相同的地址上。虽然他们包含相同的字符,特们的地址不同。
c风格字符的比较应使用strcmp()函数来比较。该函数接受两个字符串地址作为参数。这意味着参数可以是指针、字符串常量或字符数组名。如果两个字符串相等,该函数将返回0;如果第一个字符串按ASSCI码顺序排在第二个字符串之后,则strcmp()将返回一个正数值。如果第一个字符串按ASSCI码顺序排在第二个字符串之前,则strcmp()将返回一个负数值。
#include <iostream>
using namespace std;
int main(void)
{
char word[5] = "?ate";
for (char ch = 'a'; strcmp(word, "mate"); ch++)
{
cout << word << endl;
word[0] = ch;
}
cout << "After loop ends,word is " << word << endl;
system("pause");
return 0;
}
5.1.15比较string类字符串
#include <iostream>
using namespace std;
int main(void)
{
string word = "?ate";
for (char ch = 'a'; word != "mate"; ch++)
{
cout << word << endl;
word[0] = ch;
}
cout << "After loop ends,word is " << word << endl;
system("pause");
return 0;
}
关系运算符可以用于string 对象
5.2while循环
5.2.2等待一段时间:编写延时循环(p148)
clock()返回的时间不一定是秒;clock()函数的返回类型在某些系统上可能是long,在另一些系统上是unsigned long类型或其他类型。
头文件ctime。定义了一个符号常量——CLOCKS-PER-SEC,该常量等于每秒钟包含的系统时间单位数。系统时间除以CLOCKS-PER-SEC可以得到秒数。秒数乘以CLOCKS-PER-SEC可以得到以系统时间为单位的时间。ctime()将clock_t作为clock()返回类型的别名。
#include <iostream>
#include <ctime>
using namespace std;
int main(void)
{
cout << "Enter tne delay time,in seconds:";
float secs;
cin >> secs;
clock_t delay = secs * CLOCKS_PER_SEC;
cout << "starting\a\n";
clock_t start = clock();
while (clock() - start < delay)
;
cout << "done\a\n";
system("pause");
return 0;
}
5.5循环和文本输入
5.5.1使用原始的cin进行输入
cin忽略换行符和空格,因此输入中的空格没有被回显,也没有被包括在计数内。
发送给cin的输入会被缓冲,这意味着只有在用户按下回车键后,他输入的内容才会被发送给程序。因此,在运行该程序时,可以在#后面输入字符。
5.5.2使用c.get(char)进行补救
5.5.4文件尾条件
在windows10,模拟EOF是在一个新行的开头输入ctrl + z
#include <iostream>
using namespace std;
int main(void)
{
char ch;
int count = 0;
cin.get(ch);
while (cin.fail() == false)//test for EOF
{
cout << ch;
++count;
cin.get(ch);
}
cout << endl << count<< "characters read\n";
system("pause");
return 0;
}
回车也算一个字符。
常见的字符输入做法:
每次读取一个字符,直到遇到EOF的输入循环的基本设计如下:
cin.get(ch); //attempt to read a char
while (cin.fail() == false) //test for EOF
{
... //do stuff
cin.get(ch); //attempt to read another char
}
简化后
while (cin.get(ch)) //while input is sucessful
{
... //do stuff
}
可以使用int ch,并用cin.get()代替cin.get(char),用cout.put()代替cout,用EOF代替cin.fail()测试:
int ch;//for compatibility with EOF value
ch = cin.get();
while (ch != EOF)
{
cout.put(ch);
++count;
ch = cin.get();
}
如果ch是一个字符,则将循环显示它;如果ch为EOF,则循环将结束。
3.第六章 分支语句和逻辑运算符
6.2.5逻辑运算符细节
- 逻辑OR和逻辑AND运算符的优先级低于关系运算符。
- !运算符的优先级高于所有关系运算符和算术运算符。
- And运算符优先级高于or。
6.3字符函数库cctype(p179)
6.5switch语句
6.5.1将枚举量作为标签(p183)
#include <iostream>
using namespace std;
enum {red,orange,yellow,green,blue,violet,indigo};
int main(void)
{
cout << "Enter color code(0-6):";
int code;
cin >> code;
while (code >= red && code <= indigo)
{
switch (code)
{
case red:
cout << "Her lips were red." << endl;
break;
case orange:
cout << "Her hair was orange." << endl;
break;
case yellow:
cout << "Her shoes were yellow." << endl;
break;
case green:
cout << "Her nails were green." << endl;
break;
case blue:
cout << "Her sweatsuit was blue." << endl;
break;
case violet:
cout << "Her eyes was violet." << endl;
break;
case indigo:
cout << "Her mood was indigo." << endl;
break;
}
cout << "Enter color code(0-6):";
cin >> code;
}
cout << "Bye" << endl;
system("pause");
return 0;
}
6.6break和continue语句(p185)
#include <iostream>
using namespace std;
const int Arsize = 80;
int main(void)
{
char line[Arsize];
int spaces = 0;
cout << "Enter a line of text:" << endl;
cin.getline(line, Arsize);
cout << "Complete line:" << line << endl;
cout << "Line through first period:" << endl;
for (int i = 0; line[i] != '\0'; i++)
{
cout << line[i];
if (line[i] == '.')
break;
if (line[i] != ' ')
continue;
spaces++;
}
cout << endl << spaces << " spaces" << endl;
cout << "Done." << endl;
system("pause");
return 0;
}
6.7读取数字的循环
#include <iostream>
using namespace std;
const int Max = 5;
int main(void)
{
//get data
double fish[Max];
cout << "Please enter the weights of your fish." << endl;
cout << "You may enter up to " << Max << " fish<q to terminate>." << endl;
cout << "fish #1:";
int i = 0;
while (i < Max && cin >> fish[i])
{
if (++i < Max)
cout << "fish #" << i + 1 << ":";
}
//caluate average
double total = 0.0;
for (int j = 0; j < i; j++)
total += fish[j];
//report results
if (i == 0)
cout << "No fish" << endl;
else
cout << total / i << " = average weight of " << i << " fish" << endl;
cout << "Done" << endl;
system("pause");
return 0;
}
注意:
1.如果逻辑AND表达式左侧为false,则c++不会判断右侧的表达式。i < Max && cin >> fish[i]说明如果i = Max,循环将结束,而不会将下一个值读入到数组后面的位置中。
2.当用户输入不是数字时,该程序将不再读取输入。
#include <iostream>
using namespace std;
const int Max = 5;
int main(void)
{
//get data
double golf[Max];
cout << "Please enter your golf scores." << endl;
cout << "You must enter " << Max << "rounds." << endl;
int i;
for (i = 0; i < Max; i++)
{
cout << "round #" << i + 1 << ":";
while (!(cin >> golf[i]))
{
cin.clear(); //reset input
while (cin.get() != '\n')
continue; //get rid of bad input
cout << "Please enter a number:";
}
}
//calute average
double total = 0.0;
for (i = 0; i < Max;i++)
{
total += golf[i];
}
//report results
cout << total/Max << " = average score " << Max << " rounds." << endl;
system("pause");
return 0;
}
处理错误代码的关键部分
while (!(cin >> golf[i]))
{
cin.clear(); //reset input
while (cin.get() != '\n')
continue; //get rid of bad input
cout << "Please enter a number:";
}
如果省略cin.clear(),程序将拒绝读取输入。
6.8简单文件输入输出
6.8.2写入到文本文件中
使用文件输出的主要步骤如下:
- 包含头文件fstream.
- 创建一个ofstream对象。
- 将该ofstream对象同一个文件关联起来。
- 就像使用cout那样使用该ofstream对象。
#include <iostream>
#include <fstream> //for file I/O
using namespace std;
//包含头文件fstream.
//创建一个ofstream对象。
//将该ofstream对象同一个文件关联起来。
//就像使用cout那样使用该ofstream对象。
int main(void)
{
char automobile[50];
int year;
double a_price;
double d_price;
ofstream outFile; //creat object for output
outFile.open("carinfo.txt"); //associate with a file
cout << "Enter the make and model of automobile:";
cin.getline(automobile, 50);
cout << "Enter the model year:";
cin >> year;
cout << "Enter the original asking price:";
cin >> a_price;
d_price = 0.913 * a_price;
//display information on screen with cout
cout << fixed; //用一般的方式输出浮点型,例如C++程序在控制台显示的时候大一点的数,
//显示的时候使用了科学计数法,使用该命令即可像一般的方式显示
cout.precision(2); //设置精确度为2,并返回上一次的设置。
cout.setf(ios_base::showpoint); //显示浮点数小数点后面的零。
cout << "Make and model:" << automobile << endl;
cout << "Year: " << year << endl;
cout << "Was asking $" << a_price << endl;
cout << "Now asking $" << d_price << endl;
//now do exact same thing using outFile instead of cout
outFile << fixed;
outFile.precision(2);
outFile.setf(ios_base::showpoint);
outFile << "Make and model:" << automobile << endl;
outFile << "Year: " << year << endl;
outFile << "Was asking $" << a_price << endl;
outFile << "Now asking $" << d_price << endl;
outFile.close(); //done with file
system("pause");
return 0;
}
- 1.cout<<fixed;//用一般的方式输出浮点型,例如C++程序在控制台显示的时候大一点的数,显示的时候使用了科学计数法,使用该命令即可像一般的方式显示
- 2.cout.precision(2);//设置精确度为2,并返回上一次的设置。
- 3.cout.setf(ios_base::showpoint);//显示浮点数小数点后面的零。
6.8.3读取文本文件
读取文本文件的主要步骤:
- 包含头文件fstream.
- 创建一个ifstream对象。
- 将该ifstream对象同一个文件关联起来。
- 就像使用cin那样使用该ifstream对象。
检查文件是否被成功打开:
inFile.open("bolwing.txt");
if (!inFile.is_open())
{
exit(EXIT_FAILURE);
}
程序:打开用户指定的文件,读取其中的数字,然后指出文件中包含多少个值以及他们的和与均值。
#include <iostream>
#include <fstream> //file I/O support
#include <cstdlib> //support for exit()
using namespace std;
const int SIZE = 60;
int main(void)
{
char filename[SIZE];
ifstream inFile; //object for handing file input
cout << "Enter of data file:" << endl;
cin.getline(filename, SIZE);
inFile.open(filename); //associate inFile with a file
if (!inFile.is_open()) //failed to open file
{
cout << "Could not open the file " << filename << endl;
cout << "Program terminating." << endl;
exit(EXIT_FAILURE);
}
double value;
double sum = 0.0;
int count = 0; //number of items read
inFile >> value; //get first value
while (inFile.good())
{
count++; //one more item read
sum += value; //caculate running total
inFile >> value; //get next value
}
if (inFile.eof())
cout << "End of file reached." << endl;
else if (inFile.fail())
cout << "Input terminated for unknown reason." << endl;
else
cout << "Input terminated for unknown reason." << endl;
if (count == 0)
cout << "No data processed." << endl;
else
{
cout << "Item read: " << count << endl;
cout << "Sum: " << sum << endl;
cout << "Average: " << sum / count << endl;
}
inFile.close();
system("pause");
return 0;
}
注意:Windows文本文件的每行都以回车字符(\r)和换行符(\n)结尾;有些文本编辑器(比如测试时用的记事本)不会自动在最后一行末尾加上换行符。所以需要在输入最后的文本按下回车键,然后再保存文件。不然会出错!!!
4.第七章 函数——C++编程模块
7.3函数和数组
1.p213 arr[i] == *(arr + i); &arr[i] == arr +i;
将指针(包括数组名)加1,实际上是加了一个与指针指向的类型的长度(以字节为单位)相等的值。
7.5函数和C-风格字符串的函数
7.5.2返回c风格字符串的函数
函数无法返回一个字符串,但可以返回字符串的地址,这样做的效率更高。
#include <iostream>
using namespace std;
char* buildstr(char c, int n);
int main(void)
{
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;
ps = buildstr('+', 20);
cout << ps << "-DONE-" << ps << endl;
delete[]ps;
system("pause");
return 0;
}
char* buildstr(char c, int n)
{
char* pstr = new char[n + 1];
pstr[n] = '\0';
while (n-- > 0)
{
pstr[n] = c;
}
return pstr;
}
5.第8章 函数探幽
8.1C++内联函数
- 哪种函数适合定义内联函数?只有一行代码的小型非递归函数。
- 使用内联:在函数声明前加关键字inline,在函数定义前加关键字inline。
8.2引用变量
1.引用变量的主要用途:用作函数的形参。
2.通过将引用变量做参数,函数将使用原始数据,而不是其副本。
3.引用也为函数处理大型结构提供了一种方便的途径。
8.2.1创建引用变量
注意:1.必须在声明引用变量时进行初始化。
int &rodents = rats; ✔ int rat; int & rodent; rodent = rat; ❌
2.引用与某个变量关联起来,将一直效忠于它。
8.2.2将引用用作函数参数
8.2.3引用的属性和特别之处
1.如果程序员的意图是让函数使用传递给它的信息,而不对这些信息进行修改,同时又想使用引用,则应使用常量引用。
2.临时变量、引用参数和const
(1)如果实参与引用参数不匹配,C++将生成临时变量。当前,仅当参数为const引用时,C++才 允许这样做。
(2)C++在下面两种情况创建临时变量:
a.实参的类型正确,但不是左值;
b.实参的类型不正确,但可以转换为正确的类型。
(变量、数组元素、结构成员、引用和解除引用的指针都是左值)
(3)如果函数调用的参数不是左值或者与相应的const引用参数类型不匹配,则C++将创建类型正确的匿名变量,将函数调用的参数的值传给该匿名变量,并让参数引用该变量。
8.2.4将引用用于结构
8.3默认参数
- 语法:char * left(const char * str,int n = 1);
- 对于带参数列表的函数,必须从右向左添加默认值。也就是说,要为某个参数设置默认值,则必须为它右边的左右参数提供默认值。
- 好处:在设计类时,通过使用默认参数,可以减少要定义的析构函数、方法以及方法重载的数量。
- 函数只有原型指定默认值,函数定义与没有默认参数时完全相同。
8.5 函数模板
如果需要多个将同一种算法用于不同类型的函数,使用函数模板。
#include <iostream>
//函数原型
template <typename T> //or class T
void Swap(T& a, T& b);
using namespace std;
int main(void)
{
int i = 10;
int j = 20;
cout << "i,j = " << i << " , " << j << endl;
cout << "Using compiler-generated int swaper:" << endl;
Swap(i, j);
cout << "Now,i,j = " << i << "," << j << endl;
double x = 24.5;
double y = 81.7;
cout << "x,y = " << x << "," << y << endl;
cout << "Using compiler-generated double swaper:" << endl;
Swap(x, y);
cout << "Now,x,y = " << x << "," << y << endl;
system("pause");
return 0;
}
//函数定义
template<typename T>
void Swap (T &a, T &b)
{
T temp;
temp = a;
a = b;
b = temp;
}
更常见的情形是,将模板放在头文件中,并在需要使用模板的文件中包含头文件。
8.5.1重载的模板
#include <iostream>
//函数原型
template <typename T> //or class T original template
void Swap(T& a, T& b);
template<typename T> //new template
void Swap(T* a, T* b, int n);
void Show(int a[]);
const int Lim = 8;
using namespace std;
int main(void)
{
int i = 10;
int j = 20;
cout << "i,j = " << i << " , " << j << endl;
cout << "Using compiler-generated int swaper:" << endl;
Swap(i, j); //matches original template
cout << "Now,i,j = " << i << "," << j << endl;
int d1[Lim] = { 0,7,0,4,1,7,7,6 };
int d2[Lim] = { 0,7,2,0,1,9,6,9 };
cout << "Original arrays:" << endl;
Show(d1);
Show(d2);
Swap(d1, d2, Lim); //matches new template
cout << "Swapped arrays:" << endl;
Show(d1);
Show(d2);
system("pause");
return 0;
}
//函数定义
template<typename T>
void Swap(T& a, T& b)
{
T temp;
temp = a;
a = b;
b = temp;
}
template<typename T>
void Swap(T a[], T b[],int n)
{
T temp;
for (int i = 0; i < n; i++)
{
temp = a[i];
a[i] = b[i];
b[i] = temp;
}
}
void Show(int a[])
{
cout << a[0] << a[1] << "/";
cout << a[2] << a[3] << "/";
for (int i = 4; i < Lim; i++)
cout << a[i];
cout << endl;
}
8.5.2模板的局限性
解决方案:
- 重载运算符,以便能够将用算符用于特定的结构或者类。
- 为特定类型提供具体化的模板定义。
8.5.3显式具体化
1.对于给定的函数名,可以有非模板函数、模板函数和显式具体化模板函数以及他们的重载版本。
2.显式具体化的原型和定义应该以template<>打头,并通过名称指出类型。
非模板函数:void Swap(job &,job &);
模板函数:template<typename T> void Swap(T &,T &)
具体化:template<> void Swap<job>(job &,job&);
3.如果有多个原型,编译器在选原型时,非模板函数优先于具体化和常规模板,具体化优先于常规模板。
4.显式具体化示例
#include <iostream>
//函数原型
template <typename T> //or class T original template
void Swap(T& a, T& b);
struct job
{
char name[40];
double salary;
int floor;
};
//具体化
template <> void Swap<job>(job& j1, job& j2);
void Show(job& j);
using namespace std;
int main(void)
{
cout.precision(2);
cout.setf(ios::fixed, ios::floatfield);
int i = 10;
int j = 20;
cout << "i,j = " << i << " , " << j << endl;
cout << "Using compiler-generated int swaper:" << endl;
Swap(i, j); //matches original template
cout << "Now,i,j = " << i << "," << j << endl;
job sue = { "Susan Yaffee",73000.60,7 };
job sidney = { "Sidney Taffee",78060.72,9 };
cout << "Before job swapping:" << endl;
Show(sue);
Show(sidney);
Swap(sue, sidney);
cout << "After job swapping:" << endl;
Show(sue);
Show(sidney);
system("pause");
return 0;
}
//函数定义
template<typename T>
void Swap(T& a, T& b)
{
T temp;
temp = a;
a = b;
b = temp;
}
template<typename T>
void Swap(T a[], T b[],int n) //general version
{
T temp;
for (int i = 0; i < n; i++)
{
temp = a[i];
a[i] = b[i];
b[i] = temp;
}
}
template <> void Swap<job>(job& j1, job& j2) //specialization
{
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)
{
cout << j.name << ": $" << j.salary
<< "on floor" << j.floor << endl;
}
8.5.4 实例化和具体化
1.显式实例化。其语法是,声明所需要的种类——用<>符号指示类型,并在声明前加上关键字template: template void Swap<int>(int,int);
2.显式具体化:使用下面两个等价声明之一:
- template <> void Swap<int>(int&,int&);
- tempalte<> void Swap(int &,int &);
显式具体化在关键字template后包含<>,而显式实例化没有。
警告:⚠试图在同一个文件中使用同一种类型的显式实例和显式具体化将出错。
隐式实例化、显式实例化、显式具体化统称为具体化。他们的相同之处在于他们表示的都是使用具体类型的函数定义,而不是通用描述。
8.5.5编译器选择使用哪个函数版本
重载解析:
- 创建候选函数列表。其中包含与被调用函数的名称相同的函数和模板函数。
- 使用候选函数列表创建可行函数列表。
- 确定是否有最佳的可行函数,如果有,则使用它,否则该函数调用出错。
最佳可行函数从最佳到最差的顺序:
- 完全匹配。但常规函数优先于模板。
- 提升转换。(char和shorts自动转换为int,float自动转换为double)。
- 标准转换。(int转换为char,long转换为double)
- 用户定义的转换。
第9章 内存模型和名称空间
9.1单独编译
头文件中通常包含的内容:
- 函数原型
- 使用#define或const定义的符号常量
- 结构声明
- 类声明
- 模板声明
- 内联函数
一个实例:
头文件coordin.h
#ifndef COORDIN_H_ //ifndef == id not defined
#define COORDIN_H_
struct polar
{
double distance;
double angle;
};
struct rect
{
double x;
double y;
};
polar rect_to_polar(rect xypos);
void show_polar(polar dapos);
#endif // !COORDIN_H_
file1.cpp
#include <iostream>
#include "coordin.h"
using namespace std;
int main()
{
rect rplace;
polar pplace;
cout << "Enter the x and y values:";
while (cin >> rplace.x >> rplace.y)
{
pplace = rect_to_polar(rplace);
show_polar(pplace);
cout << "Next two numbers(q to quit):";
}
cout << "Bye!" << endl;
system("pause");
return 0;
}
file2.cpp
#include <iostream>
#include <cmath>
#include "coordin.h"
using namespace std;
polar rect_to_polar(rect xypos)
{
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)
{
const double Red_to_deg = 57.2957751;
cout << "distance = " << dapos.distance;
cout << ",angle = " << dapos.angle * Red_to_deg;
cout << " degrees" << endl;
}
9.2存储连续性、作用域和链接性
C++中使用三种(在C++11中是4种)不同的方案来存储数据
- 自动存储连续性。在函数定义中声明的变量(包括函数参数)的存储连续性为自动的。在程序执行所属的函数或代码块时被创建,在执行完函数或代码块时,他们使用的内存被释放。
- 静态存储连续性。在函数定义外所定义的变量和使用关键字static定义的变量。他们在程序整个运行过程中都存在。
- 动态存储连续性。用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。有时被称为自由存储或者堆。
- 线程存储连续性(C++11)。
9.2.1作用域和链接
作用域描述了名称在文件(翻译单元)的多大范围类可见。
连接性描述了名称如何在不同单元之间共享。连接性为外部的名称可在文件间共享,连接性为内部的名称只能由一个文件中的函数共享。
9.2.2自动存储持续性
自动变量只在包含它们的函数或者代码块中可见。
#include <iostream>
using namespace std;
void oil(int x);
int main(void)
{
int texas = 11;
int year = 2011;
cout << "In main(),texas = " << texas << ",&texas = ";
cout << &texas << endl;
cout << "In main(),year = " << year << ",&year = ";
cout << &year << endl;
oil(texas);
cout << "In main(),texas = " << texas << ",&texas = ";
cout << &texas << endl;
cout << "In main(),year = " << year << ",&year = ";
cout << &year << endl;
system("pause");
return 0;
}
void oil(int x)
{
int texas = 5;
cout << "In oil(),texas = " << texas << ",&texas = ";
cout << &texas << endl;
cout << "In oil(),x = " << x<< ",&x = ";
cout << &x << endl; //start a block
{
int texas = 113;
cout << "In block,texas = " << texas << ",&texas = ";
cout << &texas << endl;
cout << "In block,x = " << x << ",&x = ";
cout << &x << endl;
} //end a block
cout << "Post-block texas = " << texas;
cout << ",&texas = " << &texas << endl;
}
自动变量存储在栈区。栈是后进先出的,即最后进入栈的变量首先被弹出。函数调用将参数的值放在栈顶,然后重新设置栈顶指针。
9.2.3静态持续变量
1.静态存储持续性变量有3种链接性:外部链接性、内部链接性、无链接性(只能在当前函数或代码块中访问)。
2.编译器将分配所固定的内存块来存储所有的静态变量。静态变量如果没有初始化,编译器将把它设置为0。在默认情况下,静态数组和结构将每个元素或成员的所有位都设置为0。这种变量被称为零初始化的。
3.创建三种静态持续变量:
............
int global = 1000; //static duration,external linkage
static int one_file = 50; //static duration,internal linkage
int main()
{
......
}
void funct1(int n)
{
static int count = 0; //static duration,no linkage
int llama = 0;
}
void funct2(int n)
{
......
}
所有静态变量在整个程序执行期间都存在。只不过count只能在funct1()中使用它。global和one_file的作用域为整个文件,即在从声明位置到文件结束的范围内都可以被使用。具体说,可以在main()、funct1()、funct2()中使用它们。由于one_file的链接性为内部,因此只能在包含上述代码的文件中使用它们。由于global的链接性为外部,因此可以在程序的其他文件中使用它。
4.静态初始化:在编译器处理文件(翻译单元)初始化变量。
动态初始化:变量在编译后初始化。
零初始化和常量表达式初始化被统称为静态初始化。首先,所有静态变量都被零初始化,而不管程序员是否显示地初始化了它。接下来,如果使用常量表达式初始化了变量,编译器再执行常量表达式初始化。
9.2.4静态持续性、外部链接性
链接性为外部的变量通常称为外部变量。他们的存储持续性为静态,作用域为整个文件。外部变量也称全局变量。
单定义规则:
- 每个使用外部变量的文件中都必须声明它;
- 变量只能有一次定义。
C++的两种变量声明:
- 一种是定义声明,它给变量分配存储空间。
- 一种是引用声明,不给变量分配存储空间,引用已有的变量。引用声明使用关键字extern,且不进行初始化。否则声明为定义,导致分配存储空间。
如果要在多个文件中使用外部变量,只需要在一个文件中包含该变量的定义,但在使用该变量的其他所有文件中,都必须使用关键字extern声明它。
external.cpp
#include <iostream>
using namespace std;
//external variable
double warming = 0.3; //warming defined
void update(double dt);
void local();
int main(void)
{
cout << "Global warming is" << warming << "degrees." << endl;
update(0.1);
cout << "Global warming is" << warming << "degrees." << endl;
local();
cout << "Global warming is" << warming << "degrees." << endl;
system("pause");
return 0;
}
support.cpp
#include <iostream>
using namespace std;
extern double warming;
void update(double dt);
void local();
void update(double dt)
{
extern double warming;
warming += dt;
cout << "Updateing global warming is" << warming << "degrees." << endl;
}
void local()
{
double warming = 0.8;
cout << "Local warming = " << warming << "degrees." << endl;
cout << "But global warming = " << ::warming << endl; //加了;;之后0.4,不加就是0.8
}
local()函数表明,定义与全局变量同名的局部变量后,局部变量将隐藏全局变量。
::作用域解析运算符。放在变量名前面时,该运算符表示使用变量的全局版本。
9.2.5静态持续性、内部链接性
twofile1.cpp
#include <iostream>
using namespace std;
int tom = 3;
int dick = 30;
static int harry = 300;
void remote_access();
int main()
{
cout << "main() reports the following address:" << endl;
cout << &tom << " = &tom," << &dick << " = &dick, ";
cout << &harry << "= &harry" << endl;
remote_access();
system("pause");
return 0;
}
twofile2.cpp
#include <iostream>
using namespace std;
extern int tom;
static int dick = 0;
int harry = 200;
void remote_access()
{
cout << "remote_access() reports the following address:" << endl;
cout << &tom << " = &tom," << &dick << " = &dick, ";
cout << &harry << "= &harry" << endl;
}
总结:
1.如果文件中定义了一个静态外部变量,其名称与另一个文件声明的常规外部变量相同,则在该文件中,静态变量将隐藏常规外部变量。
2.可使用外部变量在多文件程序的不同部分之间共享数据。可使用链接性为内部的静态变量在同一个文件中的多个函数之间共享数据。(名称空间提供了另一种共享数据的方法)
9.2.6静态存储连续性、无链接性
1.在两次函数调用之间,静态局部变量的值将会保持不变。另外,如果初始化了静态局部变量,则程序只在启动时进行一次初始化。以后再调用函数时,将不会像自动变量那样再次被初始化。
2.程序使用循环丢弃余下的字符。
cin.get(input, ArSize); //当输入的字符数大于目标数组的话,cin.get(input, ArSize)将一直读取输入。
//直到到达行尾或者读取了ArSize-1个字符为止。他把换行符留在了输入队列中。
//该程序使用cin.get(next)读取行输入后的字符。如果next是换行符,说明cin.get()读取了整行
//否则说明行中还有字符没有被读取。随后,程序使用一个循环来丢弃余下的字符
cin.get(next);
while (next != '\n')
{
cin.get(next);
}
9.2.7说明符和限定符
1.cv-限定符
- const。const全局变量的链接性为内部的。
- volatile。该关键字的作用是为了改善编译器的优化性能。
2.说明符(在同一个声明中不能使用多个说明符,但thread_local除外,它可以与static或extern结合使用)
- register 。
- static。用于作用域为整个文件的声明中,表示内部链接性。用于局部声明中,表示局部变量的存储持续性是静态的。
- extern。表明是引用声明,即声明引用在其他地方定义的常量。
- thread_local。表明变量的持续性与其所属线程持续性相同。
- mutable。用它指出,即使结构或类变量为const,其某个成员也可以被修改。
9.2.8函数和链接性
所有函数的存储持续性都自动为静态的,即在整个程序执行期间都存在。
在默认情况下,函数的链接性为外部的,即可以在文件间共享。
9.2.9语言链接性
9.2.10存储方案和动态分配
1.编译器使用3块独立的内存:一块用于静态变量,一块用于自动变量,另一块用于动态存储。
2.定位new运算符:
- 包含头文件new
- 将new运算符用于提供了所需地址的参数。
#include <new>
struct chaff
{
char dorss[20];
int slag;
};
char buffer1[50];
char buffer2[500];
int main(void)
{
chaff* p1, * p2;
int* p3, * p4;
//常规new运算符
p1 = new chaff;
p3 = new int[20];
//定位new运算符
p2 = new(buffer1) chaff;
p4 = new(buffer2) int[20];
}
定位运算符:从buffer1中分配空间给结构chaff,从buffer2中分配空间给一个包含20个元素的int数组。
#include <iostream>
#include <new> //定位new运算符头文件
using namespace std;
const int BUF = 512;
const int N = 5;
char buffer[BUF];
int main(void)
{
double* pd1, * pd2;
int i;
cout << "Calling new and placement new:" << endl;
pd1 = new double[N];
pd2 = new(buffer)double[N];
for (int i = 0; i < N; i++)
pd2[i] = pd1[i] = 1000 + 20.0 * i;
cout << "Mmemory address:" << endl;
cout << "heap:" << pd1 << " static:" << (void*)buffer << endl;
cout << "Memory contents:" << endl;
for (i = 0; i < N; i++)
{
cout << pd1[i] << " at " << &pd1[i] << ";";
cout << pd2[i] << " at " << &pd2[i] << endl;
}
cout << endl;
double* pd3, * pd4;
pd3 = new double[N];
pd4 = new(buffer)double[N]; //overwrite old data
for (i = 0; i < N; i++)
pd3[i] = pd4[i] = 1000 + 40.0 * i;
cout << "Memory contents:" << endl;
for (i = 0; i < N; i++)
{
cout << pd3[i] << " at " << &pd3[i] << ";";
cout << pd4[i] << " at " << &pd4[i] << endl;
}
cout << endl;
cout << "Calling new and placement new a third time:" << endl;
delete[] pd1;
pd1 = new double[N];
pd2 = new(buffer + N * sizeof(double)) double[N];
for (i = 0; i < N; i++)
pd2[i] = pd1[i] = 1000 + 60.0 * i;
cout << "Memory contents:" << endl;
for (i = 0; i < N; i++)
{
cout << pd1[i] << " at " << &pd1[i] << ";";
cout << pd2[i] << " at " << &pd2[i] << endl;
}
delete[] pd1;
delete[] pd2;
system("pause");
return 0;
}
9.3名称空间
9.3.1传统的C++名称空间(p325)
声明区域:可以在其中进行声明的区域。
潜在作用域:变量的潜在作用域从声明点开始,到其声明区域的结尾。
作用域:变量对程序而言可见的范围。
9.3.2新的名称空间特性
pail未被限定的名称 Jack::pail限定的名称
1.using声明和using编译指令
using声明由被限定的名称和它前面的关键字using组成,using Jack::pail;
using编译指令:
- 在全局声明区域中使用using指令,将使该名称空间的名称全局可用。using namespace std;
- 在函数中使用using编译指令,将使其中的名称在该函数中可用。
第10章 对象和类
1.什么是类?
答:类是用户定义的类型的定义。类声明指定了数据如何存储,同时指定了用来访问和操纵这些数据的方法。(类成员函数)
2.类如何实现抽象、封装和数据隐藏?
答:类表示人们可以通过类方法的公有接口对类对象执行的操作,这就是抽象。类的数据成员可以是私有的(默认值),这意味着只能通过成员函数来访问这些数据,这就是数据隐藏。实现的具体细节都是隐藏的,这就是封装。
3.对象和类之间的关系是什么?
类定义了一种类型,包括如何使用它。对象是一个变量或其他数据对象(如由new生成的),并根据类定义被创建和使用。类和对象之间的关系同标准类型与其变量之间的关系相同。
4.除了是函数之外,类函数成员和类数据成员之间的区别是什么?
答:如果创建给定类的多个对象,则每个对象都有其自己的数据内存空间;但所有对象都使用同一组成员函数。
5.类构造函数在何时被调用,类析构函数呢?
答;在创建类对象或显式调用构造函数时,类的构造函数都将被调用。当对象过期时,类的析构函数将被调用。
8.什么是默认构造函数,拥有默认构造函数有何好处?
答:默认构造函数是没有参数或者所有参数都有默认值的构造函数。拥有默认构造函数后,可以声明对象,而不初始化它,即使已经定义了初始化构造函数。他还使得能够声明数组。
10.this和*this是什么?
答:this指针是类方法可以使用的指针,它指向用于调用方法的对象。因此this是对象的地址,*this是对象本身。
第十一章 使用类
1.注意:(p383)不要返回指向局部变量或临时对象的引用。函数执行完毕后,局部变量和临时对象将消失,引用将指向不存在的数据。
Time Time::Sum(const Time& t)const
{
Time sum;
sum.minutes = minutes + t.minutes;
sum.hours = hours + t.hours + sum.minutes / 60;
sum.minutes %= 60;
return sum;
}
节选部分代码参数使用引用的目的是提高效率。传递引用,速度更快,使用的内存将更少。而返回值不是引用,是因为返回的sum是局部变量,在函数结束时将被删除,引用将指向一个不存在的对象。
2.友元函数和成员函数之间的区别是什么?
答:成员函数是类定义一部分,通过特定的对象来调用。成员函数可以隐式得访问调用对象的成员,而无需使用成员运算符。友元函数不是类的组成部分,因此被称为直接函数调用。友元函数不能隐式地访问类成员,而必须将成员运算符用于作为参数传递的对象。