第四章 循环和关系表达式
for 语句,while 语句 与 C语言相同,略
递增(++)/(- -)递减运算符
这两个运算符都有两个变体,如:x++;++x . 两个版本对操作数的影响是一样的,但影响的时间不同。就像先付款后到货 ;先到货后付款一样。
下面看一个简单的程序:
#include<iostream>
int main()
{
using std :: cout;
int a = 20;
int b = 20;
cout << "a = " << a << ": b = " << b << "\n";
cout << "a++ = " << a++ << ": ++b = " << ++b << "\n";
cout << "a = " << a << ": b = " << b << "\n";
return 0;
}
运行结果:
a = 20: b = 20
a++ = 20: ++b = 21
a = 21: b = 21
粗略的讲,a++ 意味着使用 a 的当前值计算表达式,然后将 a 的值加 1 ; 而 ++b 的意思是先将 b 的值加 1 ,然后使用新的值来计算表达式。
例如,有下面的关系:
int x = 5;
int y = ++x; // change x, then assign to y
// y is 6 , x is 6
int z = 5;
int y = z++; //assign to y, then change z
// y is 5 , z is 6
递增/递减运算符和指针
将递增运算符用于指针时,将把指针的值增加其指向的数据类型占用的字节数,这种规则适用于对指针的递增和递减:
double arr[5] = {21.1,32.8,23.4,45.2,37.4};
double *pt = arr; // pt points to arr[0] ,i.e. to 21.1
++pt; // pt poionts to arr[1] ,i.e. to 32.8
也可以结合使用这些运算符和 *运算符来修改指针指向的值。将* 和 ++ 同时用于指针时提出了这样的问题:将什么解除引用,将什么递增。这取决于运算符的位置和优先级。前缀~和解除引用运算符的优先级相同,以从右到左的方式进行结合。后缀~比解除引用优先级高,以从左到右的方式进行结合。
所以 *++pt 的含义如下:先将 ++ 应用于pt (因为 ++ 位于 * 的右边),然后将 * 应用于被递增后的pt :(下面的语句都是根据上面的程序独立进行)
double x = *++pt; // arr[1] or 32.8
另一方面, ++*pt 意味着先取得 pt 指向的值,然后将这个值加1 :
double x = ++*pt; // arr[0]+1
圆括号对指针解除引用,然后得到21.1,然后值加1 得 22.1:
double x = (*pt)++ ; // arr[0]+1
最后看这个组合:
#include<iostream>
int main()
{
using namespace std;
double arr[5] = {21.1,32.8,23.4,45.2,37.4};
double *pt = arr; // pt points to arr[0] ,i.e. to 21.1
// pt poionts to arr[1] ,i.e. to 32.8
double x = *pt++ ; // arr[0]+1
cout << x<< " " << *pt <<endl;
return 0;
}
// x==21.1 *pt == 32.8
后缀运算符 ++ 用于 pt 而不是 *pt。
用while 语句 编写延时循环:
// waiting.cpp
#include<iostream>
#include<ctime> // describes clock() function
int main()
{
using namespace std;
cout << "Enter the 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";
return 0;
}
do-while循环与C语言相同,略。
第五章 分支语句和逻辑运算符
字符函数库 cctype
这玩意能有多方便呢?
例如之前判断 ch 是不是字符的时候 :
if((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))
现在:
if(isalpha(ch))
是不是感觉之前学了个寂寞...Hhhhhhhh
cctype中的字符函数:
isalnum() | 如果参数是字母或数字,返回true |
isalpha() | 如果参数是字母,返回true |
iscntrl() | 如果参数是控制字符,返回true |
isdigit() | 如果参数是数字(0-9) , 返回true |
isgraph() | 如果参数是除空格之外的打印字符,返回true |
islower | ~~小写字母,返回true |
isprint() | ~~打印字符(包括空格),返回true |
ispunct() | ~~标点符号,~~ |
isspace() | ~~空白字符,~~ |
isupper() | ~~大写字母,~~ |
isxdigit() | 十六进制数字,~~ |
tolower() | 如果参数是大写字符,则返回小写,否则返回原参数 |
toupper() | 如果参数是小写字符,则返回大写,否则返回原参数 |
?: 运算符
通用格式: expression1 ? expression2 : expression3
如果 expression1 为 true ,则整个条件表达式的值是 expression2 的值,否则为expression3的值
int c = a > b ? a : b;
// 与下面语句等效
int c;
if (a > b){
c = a;
}else{
c = b;
}
break 与continue 语句与C语言相同,略。
第六章 函数——C++ 的编程模块
复习函数的基本知识
函数参数和按值传递
C++ 通常按值传递参数。用于接收传递值的变量被称为形参,传递给函数的值被称为实参。
如果在main()中声明了一个名为 num的变量,同时在另一个函数也声明一个名为 num的变量,他们却完全不同。
下面看一个程序:
// twoarg.cpp
#include<iostream>
using namespace std;
void nchars(char,int);
int main()
{
int times;
char ch;
cout << "Enter a character: ";
cin >> ch;
while(ch != 'q')
{
cout << "Enter a integer: ";
cin >> times;
nchars(ch,times);
cout << "\nEnter another character or "
<< "press the q-key to quit: ";
cin >> ch;
}
cout << "The value of times is " << times << ".\n";
cout << "Bye.\n";
return 0;
}
void nchars(char c,int n)
{
while(n-- > 0)
{
cout << c;
}
}
说明在函数中修改形参的值不会影响调用程序中的数据。
函数和数组
函数是处理更复杂的数据类型(如数组和结构)的关键。首先,考虑函数接口所涉及的内容。函数需要知道要对哪个数组进行累计,因此需要将数组作为参数传递给它。为使函数通用,而不限于特定长度的数组,还需要传递数组长度。这里唯一的新内容是:需要将一个形参声明为数名。 下面来看看一个函数头及其它部分:
int arr(int sum[],int n);
这看起来合理,但方括号指出 sum是一个数组,而方括号则表明,可以将任何长度的数组传递给该函数。但实际情况并非如此:sum实际上不是数组,而是指针。 所以可以用 int *sum 代替 int sum[ ] .
函数如何使用指针来处理数组
在大多数情况下,C++将数组名视为指针。数组名解释为其第一个元素的地址:
sum == &sum[ 0 ] ;
该规则有些例外。首先,数组声明使用数组名来标记存储位置;其次,对数组名使用 sizeof 将得到整个数组的长度(以字节为单位);将地址运算符& 作用于数组名时,将返回整个数组的地址。
如果像我一样傻傻分不清,可以记这两个:
sum[i] == *(sum + i) ;
&sum[i] == sum+i ;
//将指针包括数组名 加 1,实际上是加上一个与指针指向的类型的长度(以字节为单位)相等的值。
//对于遍历数组而言,使用指针加法和数组下标是等效的。
注意:为将数组类型和元素数量告诉数组处理函数,请通过两个不同的参数来传递他们:
void fabbonain(int arr[],int size);
而不要试图使用方括号表示法来传递数组长度:
void fabbonain(int arr[size]);
用 const 保护数组
使用普通参数时,函数使用的是数据的副本,不会意外地修改普通参数的原始数据;然鹅,接受数组名的函数将使用原始数据,为防止原始数据被无意修改,可这么做:
void fabbonain(const int arr[],int size);
个人的理解是:这就是我们在各种网络平台遇到的 " 只读 "。
使用数组区间的函数
除了上面所说的用两个参数向函数传递数据,还有另一种给函数提供所需信息的方法,即指定元素区间(range),这可以通过传递两个指针来完成:一个指针标识数组的开头,另一个指针标识数组的尾部。
例如,假设有这样的声明: double sum[ 20 ] ;
则指针 sum 和 sum + 20 定义了区间。首先,数组名 sum 指向第一个元素。表达式 sum + 19 指向最后一个元素(即 sum [19 ]),因此,sum + 20 指向数组结尾后面的一个位置。
指针和const
函数和二维数组
例如,假设有如下代码:
int data [3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int total = sum(data,3);
则sum()的原型是什么样的呢?函数为何将行数(3)作为参数,而不是列数(4)呢?
data 是一个数组名,该数组有 3 个元素。第一个元素本身是一个数组,由 4 个int 值组成。因此 data 的类型是指向由 4个 int组成的数组的指针,因此正确的原型如下:
int sum(int (*ar2)[4], int size);
int sum(int ar2[][4], int size);
// 第二种格式更容易理解
// sum()在声明参数ar2时,没有使用const,因为这只能用于基本类型的指针
// 而ar2是指向指针的指针
数组表示法和指针表示法互换:
ar2[x][y] = *(*(ar2+x)+y);
函数和C风格字符串
将 C风格字符串作为参数的函数
(1)char 数组 ;
(2)用引号括起的字符串常量 ;
(3)被设置为字符串的地址的 char 指针 ;
但上述三种选择的类型都是 char 指针,因此可以将其作为字符串处理函数的参数:
#include<iostream>
#include<cstring>
using namespace std;
int main()
{
char ghost[15] = "galloping";
char *str = "galloping";
int n1 = strlen(ghost);
int n2 = strlen(str);
int n3 = strlen("galloping");
cout << n1 << endl;
cout << n2 << endl;
cout << n3 << endl;
return 0;
}
// n1,n2,n3 都是 9
C风格字符串与 char 数组之间的一个重要区别是,字符串有内置的结束字符,不以空字符结尾的char数组只是数组,而不是字符串。
返回C风格字符串的函数
函数无法返回一个字符串,但可以返回字符串地点地址,这样做效率更高。
下面的程序定义了一个名为 buildstr () 的函数,该函数返回一个指针。该函数接受两个参数:一个字符和一个数字。函数使用 new 创建一个长度与数字参数相等的字符串,将每个元素都初始化为该字符。然后,返回指向新字符串的指针:
#include<iostream>
char * buildstr(char c, int n);
int main()
{
using namespace std;
int times;
char ch;
cout << "Enter a character: ";
cin >> ch;
cout << "Enter a integer: ";
cin >> times;
char *ps = buildstr(ch,times);
cout << ps << endl;
delete []ps;
ps = buildstr('+', 20);
cout << ps << "-Done-" << ps << endl;
delete []ps;
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;
}
// 运行结果:
// Enter a character: h
// Enter a integer: 10
// hhhhhhhhhh
// ++++++++++++++++++++-Done-++++++++++++++++++++
函数和结构
涉及函数时,结构变量的行为更接近于基本的单值变量,所以为结构编写函数更简单。
下面是一个示例:
#include<iostream>
struct travel_time
{
int hours;
int mins;
};
const int Min_hr = 60;
travel_time sum(travel_time t1,travel_time t2);
void show_time(travel_time t);
int main()
{
using namespace std;
travel_time day1 = {5, 45}; // 5 hours,45 min
travel_time day2 = {4, 55}; // 4 hours,55 min
travel_time trip = sum(day1, day2);
cout << "Two-day total: ";
show_time(trip);
travel_time day3 = {4, 32};
cout << "Three-day total: ";
show_time(sum(trip, day3));
return 0;
}
travel_time sum(travel_time t1,travel_time t2)
{
travel_time total;
total.mins = (t1.mins + t2.mins) % Min_hr;
total.hours = t1.hours + t2.hours + (t1.mins + t2.mins)/Min_hr;
return total;
}
void show_time(travel_time t)
{
using namespace std;
cout << t.hours << " hours, "
<< t.mins << " minutes\n";
}
// 运行结果
// Two-day total: 10 hours, 40 minutes
// Three-day total: 15 hours, 12 minutes
传递结构的地址
下面是一个示例:
#include<iostream>
#include<cmath>
struct polar
{
double distance;
double angle;
};
struct rect
{
double x;
double y;
};
void rect_to_polar(const rect *pxy, polar * pda);
void show_polar(const polar * pda);
int main()
{
using namespace std;
rect rplace;
polar pplace;
cout << "Enter the x and y values: ";
while (cin >> rplace.x >> rplace.y)
{
rect_to_polar(&rplace, &pplace);
show_polar(&pplace);
cout << "Next two numbers (q to quit): ";
}
cout << "Done.\n";
return 0;
}
void show_polar(const polar * pda)
{
using namespace std;
const double Rad_to_deg = 57.29577951;
cout << "distance = " << pda->distance;
cout << ", angle = " << pda->angle * Rad_to_deg;
cout << " degrees\n";
}
void rect_to_polar(const rect *pxy,polar * pda)
{
using namespace std;
pda->distance =
sqrt(pxy->x * pxy->x + pxy->y * pxy->y);
pda->angle = atan2(pxy->y,pxy->x);
}
// 运算符 -> 是重中之重
函数和string对象
如果需要多个字符串,可以声明一个 string 对象数组,并将该数组传递给一个函数以显示其内容。
下面是一个示例:
#include<iostream>
#include<string>
using namespace std;
const int SIZE = 5;
void display(const string sa[], int n);
int main()
{
int i;
// 如果需要使用 string 数组,只需使用通常的数组声明格式即可
string list[SIZE];
cout << "Enter your " << SIZE << " favorite astronomical sights:\n";
for(i=0; i<SIZE; i++)
{
cout << i + 1 << ": ";
getline(cin,list[i]);
}
cout << "Your list:\n";
display(list,SIZE);
return 0;
}
void display(const string sa[], int n)
{
int i;
for(i=0;i<n;i++)
{
// 形参 sa 是一个指向 string 对象的指针,因此 sa[ i ] 是一个string 对象
cout << i + 1 << ": " << sa[i] <<endl;
}
}
函数指针
与数据项相似,函数也有地址。函数的地址是存储其机器语言代码的内存的开始地址。通常,这些地址对用户而言不重要,但对程序却很重要。例如,可以编写将另一个函数的地址作为参数的函数。这样第一个函数将能够找到第二个函数,并运行它。与直接调用另一个函数相比。这很笨拙,但它允许在不同时间传递不同函数的地址,这意味着可以在不同时间使用不同函数。
获取函数的地址:
获取函数地址很简单,只使用函数名(后面不跟参数)即可。
例如:
// 有函数process() 和 think()
process(think); // 传递的是think()函数的地址
process(think()); // 传递的是think()函数的返回值
声明函数指针:
声明函数指针应指定函数的返回类型以及函数的特征标(参数列表)。
例如:
double pam(int);
double (*pf)(int);
// pam 替换为 (*pf)
// 由于 pam是函数,因此(*pf)也是函数
// 所以 pf 就是函数指针
两者的返回类型和特征标必须相同!
另外,由于括号优先级比 * 高 ,所以:
double (*pf)(int);
// pf 是指向函数的指针
double *pf(int);
// pf() 是一个返回指针的函数
在使用函数指针时,比较棘手的是编写原型。
下面是一个示例:
#include<iostream>
double betsy(int lns)
{
return 0.05 * lns;
}
double pam(int lns)
{
return 0.03 * lns + 0.0004 * lns * lns;
}
void estimate(int lines, double (*pf)(int))
{
using namespace std;
cout << lines << " lines will take ";
cout << (*pf)(lines) << " hour(s)\n";
}
int main()
{
using namespace std;
int code;
cout << "How many lines of code do you need? ";
cin >> code;
cout << "Here is Betsy's estimate:\n";
estimate(code,betsy);
cout << "Here is Pam's estimate:\n";
estimate(code,pam);
return 0;
}
深入探讨函数指针
下面通过一个示例演示使用函数指针时面临的一些挑战:
const double * f1(const double ar[],int n);
const double * f2(const double [],int);
const double * f3(const double *, int);
// 这三个函数的特征标实际上是相同的!
// ar[] 可简化为 []
// * ar 可简化为 *