1、写一个Hello World
在上图这个程序中,我们需要一个能具备输出Hello World功能的函数. C++中的函数是啥?
函数
函数是一段能被反复调用的代码,可以接收输入,进行处理并(或)产生输出。它主要由返回类型、函数名、形参列表、函数体组成。
返回类型
表示了函数返回结果的类型,可以为void。如上图中int main()中的int。
函数名
用于函数调用。如上图中int main()中的main。
形参列表
表示函数接收的参数类型(可以为空,可以为void,可以无形参)。int main()中()里面表示main函数所包含的所有形参列表(函数里可以有形式参数,我们调用该函数时,是用实际的参数传进该函数,替换掉形参)
以下代码是有一个形参的情况。
#include <iostream>
void fun(const char* pInfo)
{
std::cout << pInfo << std::endl;
return;
}
int main()
{
fun("Hello World");
fun("Hello xzx");
}
以下代码是形参列表里面有多个形参的情况。在main函数里面调用fun函数,把实参"Hello World"赋给形参pInfo,把实参0赋给形参pValue(和JAVA中的方法很像)。
#include <iostream>
void fun(const char* pInfo, int pValue )
{
std::cout << pInfo << std::endl;
}
int main()
{
fun("Hello World",0);
fun("Hello xzx",9);
}
形参列表可以为空
#include <iostream>
void fun()
{
std::cout << "xzx" << std::endl;
}
int main()
fun();
}
形参列表也可以写viod。但通常省略
#include <iostream>
void fun(void)
{
std::cout << "xzx" << std::endl;
}
int main()
fun();
}
形参列表可以无形参变量
形参列表中有两个形参,但第二个形参 int 我们没有给他名称,在fun函数里面我们没办法获取这个形参所对应的实参值。但这样可以保证main函数调用fun函数时,接口还是fun(“xxx”, 0)的形式,接口不变。在mian函数里面调用fun函数,把实参"Hello World"赋给形参pInfo,但无法把实参0赋给fun函数中的另一个形参(因为它没有名称,在fun函数里面我们没办法获取这个形参所对应的实参值)。
#include <iostream>
void fun(const char* pInfo, int)
{
std::cout << pInfo << std::endl;
return;
}
int main()
{
fun("Hello World", 0);
fun("Hello xzx", 9);
}
函数体
包含了函数具体的执行逻辑。
main函数
main函数是特殊的函数,作为整个程序的入口。操作系统在调用c++程序时,最开始调用的函数就是main函数。
int main()
fun("Hello World", 0);
fun("Hello xzx", 9);
return 0;
c++定义函数时不加void的话,都需在函数体最后写return,但main函数例外,可以不写,默认return 0。其次main函数的返回类型一定是int,表示程序的返回值。通常使用0来表示正常返回。
形参列表可以为空(main函数的形参列表要么为空,要么为:int, char []*)
int main(int argc, char* argv[])
{
fun("Hello World", 0);
fun("Hello xzx", 9);
}
形参名称可变,但类型不能变。
int main(int a, char* r[]){
fun("Hello World", 0);
fun("Hello xzx", 9);
return 0;
}
形参名称没有也可以,形参类型在就行
int main(int, char* []){
fun("Hello World", 0);
fun("Hello xzx", 9);
return 0;
}
内建类型
(内建)类型可为一段存储空间赋予实际的意义。我们熟知的基本数据类型也属于内置类型,如int, byte等(byte占一个字节,int占4个字节)。 那么,我们如何来引用这些数据呢?以下面代码为例,我们通过pInfo,pValue来引用内存中所定义的某一块字节,从而获取数据。如 fun(“Hello World”,0) 中的fun传入 “Hello World”,0 之后,系统会找一段内存来存储"Hello World",再找另外一段内存来存储0,再使用pInfo,pValue来引用这段内存。
#include <iostream>
void fun(const char* pInfo, int pValue )
{
std::cout << pInfo << std::endl;
return;
}
int main()
{
fun("Hello World",0);
fun("Hello xzx",9);
}
语句
C++程序是一组数组,而每个数组又是一组语句。语句表明了需要执行的操作。
主要可划分为:表达式+分号(以分号作为结束)的语句、语句块、if/while等语句。
注释
会被编译器忽略的内容(用于编写说明或去除不使用的语句)。它有两种注释形式:/**/ 与//
2. 系统I/O
2.1. iostream
iostream是标准库所提供的IO接口,用于与用户交互。我们可以使用 #include < iostream > 这个头文件实现系统IO(头文件通过include来引用)。
2.1.1. #include “”和#include < >的区别
如果使用#include “”,系统会从当前目录开始寻找头文件。如:#include “myheader.h”,系统会在write helloworld.cpp所在的头文件目录下寻找myheader.h。 如果使用#include <>,系统会从操作系统的环境变量所指示的路径寻找头文件。
2.1.2. 输入流
cin;(用户键盘输入)
2.1.3. 输出流
cout / cerr / clog(通常输出到屏幕上)
输入流、输出流在程序中的简单使用如下:
#include <iostream>
int main()
{
int x;
std::cout << "How old are you:"; std::cin >> x;
std::cout << "You are" << x;
}
2.1.4. 输出流的区别:
输出目标不同(cerr输出的是错误信息);
是否立即刷新缓冲区
cerr 和 clog的区别:cerr输出的是错误信息,我们需要尽快的看到这些信息,从而使我们能更加全面的对系统进行了解,故cerr会立即刷新缓存区。但clog是日志信息,采用的是不立即刷新缓存区的方式。
如果我们想立即看到我们打印的东西,但没看到打印输出时,可能是缓冲区没有被刷新。那应该咋办?
一方面,可以使用cerr,cerr只要往外输出了,就刷新缓存区了。
另一方面,若使用cout或clog时也想刷新缓存区(使用cout时,是在程序结束时才刷新缓存区),则可通过std::fush ; 或std::endl来刷新缓存区。
如:
#include <iostream>
int main()
{
std::cout << "output from cout" << std::flush;}
在执行到flush时,就会刷新缓存区。这样就能保证程序执行完这条语句之后, “output from cout” 这个信息就已经能被终端用户所看到。 而如果使用std::endl,不仅会刷新缓存区,还会换行: 但一般只有在必要时才刷新缓存区。
2.2. 名字空间
在C/C++中,变量、函数和类都是大量存在的,这些变量、函数、类的名称都将作用于全局作用域中,可能会导致很多的冲突。所以,C++中引入namespace关键字,目的是对标识符的名称进行本地化,以避免命名冲突或名字污染。
2.2.1. 如何设置namespace?
namespace NameSpace1
2.2.2. 如何在函数中调用名字空间中的函数?
如:main函数中如何调用名字空间中的fun函数?如果在main函数中,想调用NameSpace1 中的fun函数,像以下代码这么写,系统会认为是在全局名字空间中调用fun函数,但实际上全局名字空间中并没有fun函数。而且也不知道想调用的是NameSpace1 中的fun还是NameSpace2中的fun。
#include <iostream>
namespace NameSpace1
{
void fun( )
{ }
}
namespace NameSpace2
{
void fun()
{ }
}
int main()
{
fun( );
}
其实,访问名字空间中元素的 3 种方式:域解析符 :: ; using 语句;名字空间别名。
域解析符 ::
使用域解析符 ::,在main函数在调用NameSpace1中的fun函数。
#include <iostream>
namespace NameSpace1
{
void fun( )
{ }
}
namespace NameSpace2
{
void fun()
{ }
}
int main()
{
NameSpace1::fun( );
}
使用域解析符 ::,在main函数在调用NameSpace2中的fun函数。
#include <iostream>
namespace NameSpace1
{
void fun( )
{ }
}
namespace NameSpace2
{
void fun()
{ }
}
int main()
{
NameSpace2::fun( );
}
using 语句
使用using namespace NameSpace1,在main函数内部,既能看到全局名字空间里所有函数,又能看到NameSpace1里面所有的函数。此时再去调用fun函数,首先会在全局名字空间里查找fun函数,如果全局名字空间里没有fun,再去NameSpace1查找。
#include <iostream>
namespace NameSpace1
{
void fun( )
{ }
}
namespace NameSpace2
{
void fun()
{ }
}
int main()
{
using namespace NameSpace1;
fun();
}
名字空间别名
为NameSpace1这个名字空间赋予一个简单的别名ns1,然后再使用类似域解析符的方式调用fun函数。
#include <iostream>
namespace NameSpace1
{
void fun( )
{ }
}
namespace NameSpace2
{
void fun()
{ }
}
int main()
{
namespace ns1 = NameSpace1;
ns1::fun();
}
2.2.3. std 名字空间
std 是c++标准库里的名字空间。
std::cout << "output from cout" << std::flush;
如,cout函数,flush函数都包含在std这个名字空间里。访问名字空间中的变量和访问名字空间中的函数方法是同样的操作。
2.2.4. 名字空间与名称改编( name mangling )
从上一章我们知道,整个程序编译完之后会编译出.o文件,再链接成可执行文件。
假设有这么个程序:
#include <iostream>
namespace NameSpace1
{
void fun( )
{ }
int x;
}
namespace NameSpace2
{
void fun()
{ }
}
int main()
{
}
我们来看一下编译完是什么样? 其中,HelloWorld* 是可执行程序(链接后的结果),main.cpp.o是编译后的结果。我们使用nm命令罗列main.cpp.o所有外部链接(链接:比如说,我要在一个目标文件里想要调用fun1,而第二个目标文件里有fun1的定义,我们就要把这两个目标文件链接起来): T main函数是一个外部链接,B _ZN10NameSpace11xE是整型变量x的链接名称。T _ZN10NameSpace13funEv是NameSpace1中fun的链接名称,T _ZN10NameSpace23funEv是NameSpace2中fun的链接名称。
我们在源代码中的NameSpace1和NameSpace2中定义了fun函数和x变量,但在外部链接中没有找到fun和x,为什么呢?
因为,NameSpace1和NameSpace2都有fun,这是两个不同的fun定义,都需要对外提供链接,即提供两个不同的链接。故不能把两个不同的链接名字都叫fun,如果都叫fun,就没办法区别这两个不同的链接了,故c++内部会引入mangling机制,会对fun的链接名称自行改编。
我们可把链接名字变回去:
2.3. C / C++ 系统IO比较
printf: 使用直观,但容易出错
cout: 不容易出错,但书写冗长
C++ 20格式化库:新的解决方案(把一段内容解析成字符串,但很多编译器未支持)
#include <iostream>
#include <cstdio>
int main( )
{
int x = 9;
std::cout << "I have " << x << " pens\n";
printf("I have %d pens\n", x);
}
|