*****代码在Ubuntu g++ 5.31 / clang++ 3.8(C++11)下编写调试*****
每个C++程序必须有一个main( )函数,main( )函数的返回值也必须是int类型,操作系统通过调用main( )函数来运行C++程序。
1.函数格式
函数:C++的函数是一个能够完成一个功能的模块或者说是一段命名了的代码块。
完整函数的组成:
①返回类型:不一定有返回值,但一定有返回类型,无返回值时,使用void来说明类型
②函数名:根据名字标识完成特定功能的模块,必须存在 //lambda匿名函数除外
③形参列表:可能没有参数传入,但是一对括号必须存在
④函数体:一个完整的函数必须有函数体,里面包含要实现的功能。
下面为一个完整函数的例子:
int main()
{
return ;
}
如果main( )函数的返回类型是非int的,编译时将会报错,错误如下:
error: ‘::main’ must return ‘int’
double main(int argc,char *argv[])
若函数的返回值与函数事先定义的返回类型不一致时,会将返回值隐式转换到事先定义的类型。例子如下:
#include <iostream> using namespace std; int main(int argc,char *argv[])
{ return 16.5;
}
以上返回值是一个double类型的16.5,使用clang++编译时会得到提示:
first.cpp::: warning: implicit conversion from 'double' to 'int' changes value from 16.5 to
[-Wliteral-conversion]
return 16.5;
~~~~~~ ^~~~
warning generated.
而g++编译时则无任何提示,编译运行后查看返回值,可以看到返回的是int的16
另外,在C/C++标准中,是允许main( )没有return语句的,如果没有return语句,则在程序的末尾隐式插入一条return 0;语句。
在UNIX/Linux系统中,main( )函数的返回值可以被用来判断程序的执行情况,下面是几个常见的返回值
xit Code Number | Meaning | Example | Comments |
---|---|---|---|
1 | Catchall for general errors | let "var1 = 1/0" | Miscellaneous errors, such as "divide by zero" and other impermissible operations |
2 | Misuse of shell builtins (according to Bash documentation) | empty_function() {} | Missing keyword or command, or permission problem (and diff return code on a failed binary file comparison). |
126 | Command invoked cannot execute | /dev/null | Permission problem or command is not an executable |
127 | "command not found" | illegal_command | Possible problem with $PATH or a typo |
128 | Invalid argument to exit | exit 3.14159 | exit takes only integer args in the range 0 - 255 (see first footnote) |
128+n | Fatal error signal "n" | kill -9 $PPID of script | $? returns 137 (128 + 9) |
130 | Script terminated by Control-C | Ctl-C | Control-C is fatal error signal 2, (130 = 128 + 2, see above) |
255* | Exit status out of range | exit -1 | exit takes only integer args in the range 0 - 255 |
2.源文件、代码、编译,以及执行与程序返回值
源文件就是包含代码的那个文件,代码即所写的程序文字,编译是将代码翻译成二进制的过程,编译后会生成新的可执行文件。
在linux中执行一个程序(非脚本执行文件)的方式就是直接执行,无需bash来加载,可以使用绝对路径,也可以使用当前路径。绝对路径就是从根目录开始,一直写到可执行文件的文件名,然后回车执行。linux默认不搜索当前路径,因此使用当前路径时,要显式指出当前的路径,指出当前路径的方法是"./"(一个点和一个斜杠,不含引号),例如编译后的可执行文件名叫ex,在当前路径执行就是./ex。
在linux下编译时,命令如下:
g++ filename.cpp -o file_exec -std=c++11 //使用g++编译
clang++ filename.cpp -o file_exec -std=c++11 //或者使用clang++编译
上面2个命令二者二选一,使用哪个取决于你安装了哪个编译器。
命令解释:
g++或者clang++是指定编译器,其后的filename.cpp是你所要编译的源文件,即代码所在文件,-o选项是指定编译后的程序名(即output),-std=c++11选项是使用c++11标准,当然这里只是一个简单的程序,并没有用到c++11特性,该选项可选可不选。
如果你忘记用-o name选项告诉编译器可执行程序的名字,编译器就会把程序放在一个名为a.out的文件里(a.out的含义是assembler output,即汇编输出)。如果你确信编译了一个程序但又找不到它,别忘了看看有没有a.out文件!在UNIX的早期历史中,想在系统上玩游戏的人通常把游戏作为a.out来运行,以避免被系统管理员捉到,因此一些UNIX系统每晚会定期地删除所有名为a.out的文件。 ——来自Linux程序设计:第4版/(英)马修(Matthew, N.),(英)斯通斯(Stones,R.)
程序的返回值使用echo $?来查看,$?是linux默认的变量,专门用于查询上一程序的返回值。
编译程序可以使用GNU的g++或者clang++来编译,语法是g++ file.name -o file或者clang++-3.5 file.name -o file,其中-o选项是指定编译后的文件名
C++源文件的后缀名有: .cc 、.cpp 、.cxx 、.cp 、.C(大写的C)
3.标准输入输出对象,输入输出流
C++一共有四个标准输入输出对象:cin、cout、cerr、clog,其中cin是用来从流读出数据,cout用来向流写入数据。
输入和输出并不是C++语言中的正式组成成分。C和C++本身都没有为输入和输出提供专门的语句结构。输入输出不是由C++本身定义的,而是在编译系统提供的I/O库中定义的,该库是iostream库,这个库是g++在编译时自动链接的,gcc也能编译c++文件,但默认只能链接c语言的库,而无法自动链接c++库,g++可以自动链接c++库。
造成这个问题的原因是:
这是在那个计算机运行还很慢,CPU周期代价还很昂贵的时期遗留下来的历史问题。
C++标准输入输出定义了4个IO,分别为:CIN(标准输入)、COUT(标准输出)、CERR(标准错误)、CLOG,前三个在linux下是自动在当前程序下被打开的。也即任何在linux下的程序,操作系统会默认为程序打开3个文件:标准输入\输出\错误 // Linux下一切皆文件 :)
四个IO是4个基本对象,是istream和ostream类型的对象。类似于int类型的变量。
之所以定义流,是为了跨平台,统一接口,方便开发,屏蔽硬件细节,所以抽象了这一概念。
标准输入输出存在缓冲和非缓冲的概念,标准错误是非缓冲的,一旦出现错误,就立马输出错误。
缓冲的目的是防止每次读写一次文件,就要CPU中断或者文件系统驱动一次硬件,使用缓冲,可以将多次的改动一次性写入到文件,减少中断和硬件驱动,提高效率。
关于endl操纵符:它不仅结束当前行,更重要的是能刷新缓冲!有的时候明明打印了,但看不到输出,很可能是输出被缓存在缓冲里面,这时可以用endl来刷新缓冲,以写到设备中,比如显示器。通常你要是在程序使用了cout,建议配对使用endl。
4.标准库、命名空间、连续输入输出
对于使用了标准库的程序,必须包含标准库的头文件,包含标准库的头文件是使用 #include 指令来完成的, #incude 指令可以放在程序的任何地方,但是这个任何地方并不是指可以放在程序函数的内部,而是函数或者类的外面,通常 #include 指令是放在源码开头处的。另外, #include 指令不允许换行,该指令必须和其后面引入的头文件在同一行上。
C++头文件的后缀名有: .h 、.H 、.hxx 、.hpp 以及不使用后缀
输出运算符<<,<<是一个运算符!就如同 1+2中的“+”号一样,是用来计算的,只不过其“计算”与数学运算的“直觉感官”不太一样,此运算符作用是将符号右侧的值写入到左侧ostream对象中去,运算的返回来的结果还是左侧的ostream对象,所以可以连写,形成链式表达式。又因为ostream对象是不能复制的,这就是为什么很多流运算符重载时要返回引用的原因。
例如: cout<<"first"<<"second"; 因为他是个运算符,所以可以像(a+b)+c一样带上具有优先级的括号,例如: (cout<<"first")<<"second";
考虑一下:std::cout<<"first"<<(std::out<<"second");这条语句是否正确呢?如果正确,为什么正确?如果错误,为什么错误?
重点理解输出运算符<<的本质,这在运算符重载时会用到,若未理解,很容易困惑!
输入运算符与输出运算符类似,其左侧是一个istream的流对象,右侧是一个用来存储istream流的对象。
对命名空间的理解:由于程序越来越大,一个程序由不同的人负责不同的部分,同一个项目里面不同的开发人员可能会给变量起相同的名字,这样就会导致冲突,程序异常。为了避免这个问题,引入“命名空间”这个概念,每个部分的开发者为自己的代码指定名字空间(比如依据开发者的姓名),这样在遇到有冲突的变量名时,指定变量具体所属的文件,就避免了冲突;
关于缓冲的知识,可以在操作系统的IO设备管理中学习,或者linux程序设计一类,具体可以参考相应的书籍。
5.如何注释
C++有2种注释风格,一种单行双斜杠注释,如 //这是一个注释
另外一直是多行界定符注释,如/*注释内容*/,这种注释不能相互嵌套,比如注释/* 第一层注释 /* 第二层注释 */ 第一层注释 */,这种情况下从第一个界定符到第三个界定符视为注释,最后一个*/则视为不能识别的代码。
while (condition) //注意括号
statement;
关于C++语句:C++中语句有2种,一种是以分号 “;” 结尾的,另一种是一对大括号包围的 “ { } ”的复合语句,大括号里面是是以分号 “;” 结尾的一条或者多条语句的复合,在任何要求使用语句的地方,都能用大括号的符合语句代替。
注意以下代码循环部分:
while (a>b)
if(m>n)
....;
else
....;
上面这段代码while循环后跟了if,else,并且while循环体没有加花括号,那么while循环体是到哪为止呢,我之前不理解,一直以为到第一个分号截止,直到看到这段代码,才知道if...else...这段代码是看作一个整体,因此上面的都是while循环。
输入重定向使程序能够使用文件代替键盘作为输入,输出重定向则使程序能够使用文件代替屏幕作为输出。
例如:
ls -l > lsoutput.txt
这条命令把ls命令的输出保存到文件lsoutput.txt中。重定向所包含的内容可比这个简单的例子所显示的要多得多。目前只需知道文件描述符0代表一个程序的标准输入,
文件描述符1代表标准输出,而文件描述符2代表标准错误输出。你可以单独地重定向其中任何一个,例如重定向错误输出:
find / -name SHORT > findoutput.txt //普通用户从/目录下查找时,会遇到权限问题,关于权限错误的输出可以被重定向到findoutput.txt中
事实上,你还可以重定向其他文件描述符,但对标准文件描述符0、1、2以外的文件描述符进行重定向的情况很少见。上面的例子通过>操作符把标准错误重定向到一个文件。在默认情况下,如果该文件已经存在,它的内容将被覆盖。
你也可以用>>操作符将输出内容追加到一个文件中。
练习 1.1:查阅你使用的编译器文档,确定它所使用的文件命名约定。编译并运行第2页的main程序。
我使用的编译器是GCC 5.3.1,官方文档可以在gcc.gnu.org/onlinedocs中查阅。
练习 1.2:改写程序,让它返回-1。返回值-1 通常被当作程序错误的标识。重新编译并运行你的程序,观察你的系统如何处理main 返回的错误标识。
解答:在Debian 8.2下编译成功,运行无报错(空白输出),使用 echo $? 查看程序的返回状态,得到的结果是“255”,main函数中的return, 只能使用0~255之间的值. -1 的unsigned值就是255.
练习 1.3:编写程序,在标准输出上打印Hello, World。
解答:
#include <iostream>
int main()
{
std::cout << "Hello,World." << std::endl;
return 0;
}
练习1.4:我们的程序使用加法运算符+来将两个数相加。编写程序使用乘法运算符*,来打印两个数的积。
解答:
#include <iostream>
int main()
{
int v1 = 0, v2 = 0;
std::cout << "input two numbers:" << std::endl;
std::cin >> v1 >> v2;
std::cout << "the product of " << v1 << " and " << v2 << " is " << v1*v2 << std::endl;
return 0;
}
练习 1.5:我们将所有输出操作放在一条很长的语句中。重写程序,将每个运算对象的打印操作放在一条独立的语句中。
解答:
#include<iostream>
int main()
{
int v1 = 0, v2 = 0;
std::cout << "input two numbers:" << std::endl;
std::cin >> v1 >> v2;
std::cout << "the product of ";
std::cout << v1;
std::cout << " and ";
std::cout << v2;
std::cout << " is ";
std::cout << v1*v2;
std::cout << std::endl;
return 0;
}
练习 1.6:解释下面程序片段是否合法。
std::cout << "The sum of " << v1;
<< " and " << v2;
<< " is " << v1 + v2 << std::endl;
如果程序是合法的,它输出什么?如果程序不合法,原因何在?应该如何修正?
解答:
std::cout << "The sum of " << v1; //合法,假定v1=1,则输出The sum of 1
<< " and " << v2; //不合法,因为输出运算符要求左侧是输出流对象,应修正为std::cout << " and " << v2;
<< " is " << v1 + v2 << std::endl; ////不合法,原因同第二个语句,应修正为 std::cout << " is " << v1 + v2 << std::endl;
练习1.7:编译一个包含 不正确的嵌套注释的程序,观察编译器返回的错误信息。
int main()
{
std::cout << "test of comments!"; /* /* test */ */
return 0;
}
VS 2013报错: 1>------ Build started: Project: return, Configuration: Debug Win32 ------
1> return.cpp
1>c:\users\pluse\documents\visual studio 2013\projects\return\return\return.cpp(4): warning C4138: '*/' found outside of comment
1>c:\users\pluse\documents\visual studio 2013\projects\return\return\return.cpp(4): error C2059: syntax error : '/'
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
Red Hat报错: 1.7.cpp: In function ‘int main()’:
1.7.cpp:4: error: expected primary-expression before ‘/’ token
1.7.cpp:5: error: expected primary-expression before ‘return’
1.7.cpp:5: error: expected ‘;’ before ‘return’
练习1.8:指出下列哪些输出语句是合法的(如果有的话):
std::cout << "/*";
std::cout << "*/";
std::cout << /* "*/" */;
std::cout << /* "*/" /* "/*" */;
预测编译这些语句会产生什么样的结果,实际编译这些语句来验证你的答案(编写一个小程序,每次将上述一条语句作为其主体),改正每个编译错误。
解答:
双引号里面包含的内容是直接输出的,不做注释用,因此只有第3个错误。应该修改为:
std::cout << /* "*/" */ “;
#include<iostream>
int main()
{
int val = 50, sum = 0;
while (val <= 100)
{
sum += val;
val++;
}
std::cout << "Sum of 50 to 100 inclusive is " << sum << std::endl;
return 0;
}
#include<iostream>
int main()
{
int val = 10;
while (val >=0)
{
std::cout << val<<" ";
val--;
}
std::cout << std::endl;
return 0;
}
解答:
#include<iostream>
//目前还未学习其他的循环和判定,因此仅使用while来实现
int main()
{
int a,b;
std::cout << "input two numbers:";
std::cin >> a >> b;
while (a<=b)
{
std::cout << a << " ";
a++;
}
while (a > b)
{
std::cout << b << " ";
b++;
}
std::cout << std::endl;
return 0;
}
for(INITIALIZATION;CONDITION;INCREMENT) //注意分号!
statement;
int sum = 0;
for (int i = -100; i <= 100; ++i)
sum += i;
解答:
for循环完成了-100到100的累加和,sum的终值是0。
#include <iostream> int main()
{
int sum = 0;
for (int i = 50; i <= 100; i++)
sum += i;
std::cout << "Sum of 50 to 100 inclusive is " << sum << std::endl;
return 0;
}
[练习 1.10:除了++运算符将运算对象的值增加1 之外,还有一个递减运算符实现将值减少1。编写程序,使用递减运算符在for循环中按递减顺序打印出10 到0 之间的整数。]
#include <iostream> int main()
{
for (int i = 10; i >= 0; i--)
std::cout << i << std::endl;
return 0;
}
[练习 1.11:编写程序,提示用户输入两个整数,使用for循环打印出这两个整数所指定的范围内的所有整数。]
#include <iostream> int main()
{
int a, b;
std::cout << "Enter two numbers:" << std::endl;
for ( std::cin >> a >> b; a <= b; a++)
std::cout << a << " ";
return 0;
}
#include<iostream> //书上的程序并不能很好的工作,没有提供结束的方法
int main()
{
int sum = 0, value = 0;
std::cout << "enter q to quit:"; //输入字母q来退出,实际上,任何字母都能退出,这是因为value是一个int类型,输入的q属于char类型,
while (std::cin >> value) //因此std::cin >> value会返回false从而结束循环
sum += value;
std::cout << "Sum is: " << sum << std::endl;
return 0;
}
1.4.4 if语句
if(expression) //注意if后面的括号!!
statement;
#include <iostream>
int main()
{
int curval, val;
if (std::cin >> curval)
{
int cnt = 1;
while (std::cin >> val)
{
if (curval == val)
cnt++;
else
{
std::cout << curval << " occurs " << cnt << " times " << std::endl;
cnt = 1;
curval = val;
}
}
std::cout << curval << " occurs " << cnt << " times " << std::endl;
}
return 0;
} output:
$ ./if_cpp
5
5
5
1
5 occurs 3 times
2
1 occurs 1 times
3
2 occurs 1 times
4
3 occurs 1 times
5
4 occurs 1 times
6
5 occurs 1 times
[ctrl^D] 6 occurs 1 times
#include<iostream>
//目前还未学习其他的循环和判定,因此仅使用while来实现 int main()
{
int a,b;
std::cout << "input two numbers:";
std::cin >> a >> b;
while (a<=b)
{
std::cout << a << " ";
a++;
}
while (a > b)
{
std::cout << b << " ";
b++;
}
std::cout << std::endl;
return 0;
}
#include <iostream>
#include "Sales_item.h" //使用root权限拷贝或者建立链接到/usr/include目录下,或者编译时使用 -I 选项指定该文件所在的目录 int main()
{
Sales_item item;
while (std::cin >> item)
{
std::cout << item << std::endl;
} return 0;
} //编译时需指定 -std=c++11 选项
#include <iostream>
#include "Sales_item.h"
int main()
{
Sales_item item1, item2;
std::cin >> item1 >> item2;
if (item1.isbn() == item2.isbn())
std::cout << item1 + item2 << std::endl;
return 0;
}
练习 1.22:编写程序,读取多个具有相同ISBN 的销售记录,输出所有记录的和。
/*程序缺陷:不能很好的退出*/
#include <iostream>
#include "Sales_item.h"
int main()
{
Sales_item item1, item2;
std::cout << "Enter transactions:" << std::endl;
if (std::cin >> item1)
{
while (std::cin >> item2)
{
if (item1.isbn() == item2.isbn())
item2 += item1;
else
std::cout << "Data must refer to same ISBN" << std::endl;
}
std::cout << "ISBN DATA" << item2 << std::endl;
}
else
std::cout << "NO Data?!" << std::endl;
return 0;
}
练习 1.23:编写程序,读取多条销售记录,并统计每个ISBN(每本书)有几条销售记录。
解答:
#include <iostream>
#include "Sales_item.h"
int main()
{
Sales_item item1, item2;
std::cout << "Enter transactions:" << std::endl;
if (std::cin >> item1)
{
int cnt = 1;
while (std::cin >> item2)
{
if (item1.isbn() == item2.isbn())
{
item2 += item1;
cnt++;
}
else
{
std::cout << item1 << std::endl;
item1 = item2;
cnt = 1;
}
}
std::cout << item1.isbn() << " had " << cnt << " records " << std::endl;
}
else
std::cout << "NO Data?!" << std::endl;
return 0;
}
练习1.24:输入表示多个ISBN 的多条销售记录来测试上一个程序,每个ISBN 的记录应该聚在一起。
解答:
运行上一个程序,输入多条测试即可。
练习 1.25:借助网站上的Sales_item.h 头文件,编译并运行本节给出的书店程序。
解答:
#include <iostream>
#include "Sales_item.h" int main()
{
Sales_item total;
if (std::cin >> total)
{
Sales_item trans;
while (std::cin >> trans)
{
if (total.isbn() == trans.isbn())
total += trans;
else
{
std::cout << total << std::endl;
total = trans;
}
}
std::cout << total << std::endl;
}
else
{
std::cout << "NO Data?!" << std::endl;
return -1;
}
return 0;
}