文章目录
- 一、从Hello World谈起
- 二、系统I/O
- 三、控制流
- 四、结构体与自定义数据类型
一、从Hello World谈起
#include <iostream>
void fun(const char *pInfo)
{
std::cout << pInfo << std::endl;
}
int main()
{
fun("Hello World!");
fun("Hello China!");
fun("Hello TianJin!");
}
函数:一段能被反复调用的代码,可以接收输入,进行处理并(或)产生输出。
返回类型:表示了函数返回结果的类型,可以为 void
函数名:用于函数调用
形参列表:表示函数接收的参数类型,可以为空,可以为 void ,可以无形参。多个形参之间用逗号分隔
函数体:具体的执行逻辑
main 函数:特殊的函数,作为整个程序的入口,系统会调用main函数
返回类型:一定为 int ,表示程序的返回值,通常使用 0 来表示正常返回。0返回给操作系统
C++标准规定:在main函数中如果没有返回语句,系统会默认返回0。
函数名:C++是大小写敏感的语言,只有
main
函数才是系统入口函数形参列表:可以为空,可以为两个参数
int main() { // } int main(int argc, char* argv[]) { // }
Linux系统中获取上一个命令的退出状态,使用的命令行参数为:
echo #?
(内建)类型:为一段存储空间赋予实际的意义。
类型并不是计算机(硬件)引入的概念,而是C++这门语言引入的概念,用于描述参数的信息。
语句:表明了需要执行的操作。
- 表达式+分号的语句
- 语句块
- if/while等语句
注释:会被编译器忽略的内容。
-
用于编写说明或去除不使用的语句
-
两种注释形式:
-
/**/
:块注释块注释还有一个特殊的用处:
void fun(const char* pInfo, int /*pValue*/) //方便其他开发人员阅读 { }
-
//
:行注释
-
二、系统I/O
系统IO指的是系统提供的输入输出接口,用于与用户进行交互。
#include <iostream>
int main()
{
int x;
std::cout << "How old are you: ";
std::cin >> x;
std::cout << "You are " << x << std::endl;
}
iostream
:标准库所提供的IO接口,用于与用户交互。
-
输入流:
cin
;输出流:cout
/cerr
/clog
-
几个输出流之间的区别:
-
输出目标不同
可以将
cout
/cerr
这些输出流重定向到不同的文件中 -
是否立即刷新缓冲区
及时刷新缓冲区,可以看到一些错误信息。
cerr
会立即刷新缓冲区。clog
不立即刷新缓冲区。std::cout
不会默认立即刷新缓冲区,但你可以手动使用std::flush
或std::endl
来实现这一目的。
-
-
缓冲区与缓冲区刷新:
std::endl
与std::flush
-
std::endl
:这个操纵符不仅插入一个换行符,而且还会刷新输出流。因此,当你使用std::endl
时,输出会立即显示,并且缓冲区会被清空。std::cout << "Hello, World!" << std::endl;
-
std::flush
:这个函数可以用来强制刷新输出流,无论输出的是什么内容。如果你只是想刷新缓冲区而不插入任何额外的字符,可以使用std::flush
。std::cout << "Hello, World!" << std::flush;
-
#include <iostream> #include "xx.h"
#include
指令有两种形式,使用尖括号<>
和双引号""
:
使用尖括号
<>
使用尖括号时,编译器首先会在系统的默认头文件搜索路径中查找头文件。这些默认路径由编译器的实现和操作系统决定。如果找不到,编译器可能会在用户定义的额外路径中查找。使用尖括号通常是为了包含标准库的头文件,因为这些头文件通常安装在系统的标准位置。
使用双引号""
使用双引号时,编译器首先会在包含当前文件的同一目录中查找头文件。如果当前目录中找不到,编译器会退回到系统默认的头文件搜索路径中继续查找。使用双引号通常是为了包含用户自定义的头文件或第三方库的头文件,这些文件可能位于项目的源代码目录中。
如何决定选择使用哪种形式
- 标准库:对于标准库的头文件,推荐使用尖括号,因为它们通常不位于源代码目录中,而是安装在系统的标准位置。
- 用户自定义头文件:对于项目特定的头文件,使用双引号可以确保编译器首先在当前目录中查找,这有助于避免潜在的冲突,并提高可移植性。
名字空间提供了一种将程序中的实体(如变量、类型、函数等)组织在一起的方法,同时避免了命名冲突。std名字空间是C++标准库所定义的名字空间。名字空间的使用示例如下:
namespace MyNamespace {
void foo() {
// 函数定义
}
class MyClass {
// 类定义
};
}
int main() {
// 使用 MyNamespace::foo() 或 MyNamespace::MyClass 来访问命名空间中的实体
MyNamespace::foo();
MyNamespace::MyClass myObject;
return 0;
}
访问名字空间中元素的3种方式:
-
域解析符
::
namespace MyNamespace { void foo() { // 函数定义 } class MyClass { // 类定义 }; } int main() { // 使用 MyNamespace::foo() 或 MyNamespace::MyClass 来访问命名空间中的实体 MyNamespace::foo(); MyNamespace::MyClass myObject; return 0; }
-
using声明语句;
为了避免在每次使用命名空间中的实体时都写完整的命名空间名称,可以使用
using
声明:namespace MyNamespace { void foo() { // 函数定义 } class MyClass { // 类定义 }; } int main() { using namespace MyNamespace; // 现在可以直接使用 foo() 和 MyClass 而不需要 MyNamespace:: 前缀 foo(); MyClass myObject; return 0; }
使用
using
声明的注意事项:- 使用
using
声明时要谨慎,因为它可能导致命名冲突,尤其是当多个命名空间中存在同名实体时。 - 在头文件中通常不推荐使用
using namespace
,因为这会影响包含该头文件的所有源文件。
- 使用
-
名字空间别名
C++11引入了别名声明,允许为名字空间或类型定义一个别名,使代码更简洁:
namespace MyNamespace { void foo() { // 函数定义 } class MyClass { // 类定义 }; } int main() { namespace MyNS = MyNamespace; MyNS::foo(); // 使用别名访问函数 return 0; }
名字空间与名称改编(name mangling),这是每一个编译器都会有的行为。
liujie@liujie-vm:~/Documents/demo/HelloWorld/Debug$ nm main.cpp.o
U __cxa_atexit
U __dso_handle
U _GLOBAL_OFFSET_TABLE_
0000000000000062 t _GLOBAL__sub_I__ZN10NameSpace13funEv
000000000000000e T main
0000000000000019 t _Z41__static_initialization_and_destruction_0ii
0000000000000000 B _ZN10NameSpace11xE
0000000000000000 T _ZN10NameSpace13funEv
0000000000000007 T _ZN10NameSpace23funEv
U _ZNSt8ios_base4InitC1Ev
U _ZNSt8ios_base4InitD1Ev
0000000000000000 r _ZStL19piecewise_construct
0000000000000004 b _ZStL8__ioinit
liujie@liujie-vm:~/Documents/demo/HelloWorld/Debug$ nm main.cpp.o | c++filt -t
U __cxa_atexit
U __dso_handle
U _GLOBAL_OFFSET_TABLE_
0000000000000062 unsigned short _GLOBAL__sub_I__ZN10NameSpace13funEv
000000000000000e T main
0000000000000019 unsigned short __static_initialization_and_destruction_0(int, int)
0000000000000000 B NameSpace1::x
0000000000000000 T NameSpace1::fun()
0000000000000007 T NameSpace2::fun()
U std::ios_base::Init::Init()
U std::ios_base::Init::~Init()
0000000000000000 r std::piecewise_construct
0000000000000004 bool std::__ioinit
注意:
在Linux系统中,
nm
是一个命令行工具,用于列出目标文件(通常是.o
文件)中的符号(symbol)。这些符号可以是函数、变量、常量等。nm
命令对于调试和分析程序的符号表非常有用。基本的
nm
命令用法示例:nm yourfile.o
nm
的输出通常包含三列:
- 符号类型:可以是
T
(文本)、D
(数据)、B
(bss)、R
(只读数据)、C
(常量)等,这些表示符号所在的内存段。- 符号地址:符号在内存中的地址。
- 符号名称:符号的名称
c++filt
是一个用于解码 C++ 符号的命令行工具。它主要用于将 C++ 符号(通常是经过 mangling(名称修饰)后的)转换为可读的源代码形式。在 C++ 中,为了支持函数重载和复杂的类型系统,编译器会对函数和变量的名称进行 mangling,以生成唯一的标识符。
c++filt
的使用非常简单,你只需要将 mangled 名称作为参数传递给该工具,它就会输出对应的 demangled(解码后)名称。
基本用法:
c++filt [options] [mangled_name]
-t
选项:
c++filt
的-t
选项用于输出解码后的类型信息。当你使用-t
选项时,c++filt
不仅会解码函数名,还会尝试解码参数和返回值的类型。
C / C++ 系统IO比较:
printf
: 使用直观,但容易出错cout
: 不容易出错,但书写冗长
在C++20格式化库中,提供了新的解决方案: std::format
,它提供了一种类似于 Python 的字符串格式化机制,但编译器对C++20格式化库的支持还不够,需要 C++20 兼容的编译器。
三、控制流
以猜数字为例,
#include <iostream>
int main()
{
std::cout << "Please Input a number: \n";
int y = 0;
std::cin >> y;
if(y == 42)
{
std::cout << "You are right!\n";
}
else
{
std::cout << "You are wrong!\n";
}
}
if语句用于分支选择,条件部分用于判断是否执行,返回bool值。语句部分是要执行的操作。
#include <iostream>
int main()
{
int x = 42, y = 0;
while (x != y)
{
std::cout << "Please Input a number: ";
std::cin >> y;
if(y == 42)
{
std::cout << "You are right!\n";
}
else
{
std::cout << "You are wrong!\n";
}
}
}
while语句用于循环执行,条件部分用于判断是否执行。语句部分是要执行的操作。
== 与= 操作:
=操作:用于赋值,将数值保存在变量所对应的内存中,赋值表达式的返回值为左侧的常量。
int x; x = y = 42;
==操作:用于判断两个值是否相等
可以将常量放在
==
左边以防止误用。
四、结构体与自定义数据类型
在C++中,结构体(struct
)是一种复合数据类型,它允许将不同的数据项组合成一个单一的实体。结构体在C++中有着广泛的应用,包括表示数据集合、实现数据封装以及作为函数参数传递等。
-
定义结构体:
结构体可以通过以下方式定义:
struct MyStruct { int a; double b; std::string c; // 默认构造函数 MyStruct() : a(0), b(0.0), c("") {} // 带有两个参数的构造函数 MyStruct(int ia, double ib) : a(ia), b(ib), c("Initialized") {} // 全参构造函数 MyStruct(int ia, double ib, const std::string& ic) : a(ia), b(ib), c(ic) {} };
-
初始化结构体:
结构体可以通过直接初始化或使用构造函数进行初始化,结构体的构造函数与结构体的名称相同,并且没有返回类型。
// 直接初始化 MyStruct ms = {10, 20.5, "Hello"}; // 使用构造函数(如果定义了) MyStruct ms(10, 20.5, "Hello");
-
使用结构体
结构体定义了一组数据,可以通过点(
.
)操作符访问这些数据:ms.a = 42; std::cout << ms.b << std::endl;
-
结构体与类的区别:
尽管结构体和类在C++中非常相似,但它们之间存在一些差异:
- 默认访问权限:在C++中,结构体成员的默认访问权限是
public
,而类的默认访问权限也是public
,但在C++11及以后的版本中,结构体和类都可以通过访问说明符明确指定成员的访问权限。 - 数据封装:类通常用于数据封装,而结构体更倾向于表示数据的简单集合。
- 继承:类可以用于实现继承,而结构体通常不用于继承。
- 默认访问权限:在C++中,结构体成员的默认访问权限是
-
结构体与函数:
结构体可以作为函数的参数传递,也可以作为函数的返回值:
void printStruct(const MyStruct& ms) { std::cout << "a: " << ms.a << ", b: " << ms.b << ", c: " << ms.c << std::endl; } MyStruct createStruct() { return {1, 2.5, "Example"}; }
-
匿名结构体:
C++允许在定义结构体的同时实例化,而不需要先声明类型:
auto myStructInstance = struct { int a; double b; std::string c; } {10, 20.5, "Hello"};
-
嵌套结构体:
结构体可以嵌套在其他结构体中:
struct OuterStruct { struct InnerStruct { int x; double y; }; InnerStruct inner; };
-
结构体字面量:
C++11引入了结构体字面量,允许直接创建结构体的实例:
auto ms = MyStruct{10, 20.5, "Hello"};
结构体(struct
)在C++中不仅可以包含数据成员,还可以引入成员函数,从而更好地表示函数与数据的相关性。
在结构体中定义成员函数:
struct MyStruct {
int value;
// 构造函数
MyStruct(int v) : value(v) {}
// 成员函数,用于获取当前值
int getValue() const {
return value;
}
// 成员函数,用于设置新值
void setValue(int v) {
value = v;
}
};
使用成员函数:
创建结构体对象后,可以通过点(.
)操作符调用其成员函数:
MyStruct obj(10);
int val = obj.getValue(); // 调用成员函数获取值
obj.setValue(20); // 调用成员函数设置新值
通过引入成员函数,结构体可以隐藏内部实现细节,只通过成员函数暴露操作数据的方式。