程序 g++
是将 gcc
默认语言设为 C++
的一个特殊的版本, 链接时它自动使用 C++
标准库而不用 C
标准库.
若遵循源码的命名规范并指定对应库的名字, 即可以用 gcc
来编译链接 C++
程序:
gcc main.cpp -lstdc++ -o main
gcc helloworld.cpp -lstdc++ -o helloworld
下面是一个保存在文件 helloworld.cpp
中一个简单的 C++
程序的代码:
#include <iostream>
using namespace std;
int main()
{
cout << "Hello, world!" << endl;
return 0;
}
最简单的编译方式:
g++ helloworld.cpp
由于命令行中未指定可执行程序的文件名, 编译器采用默认的 a.out
. 程序可以这样来运行:
$ ./a.out
Hello, world!
通常我们使用 -o
选项指定可执行程序的文件名, 以下实例生成一个 helloworld
的可执行文件:
g++ helloworld.cpp -o helloworld
执行 helloworld
:
$ ./helloworld
Hello, world!
如果是多个C++
代码文件, 如 runoob1.cpp, runoob2.cpp
, 编译命令如下:
g++ runoob1.cpp runoob2.cpp -o runoob
生成一个runoob
可执行文件.
g++
有些系统默认是使用C++98
, 我们可以指定使用C++11
来编译main.cpp
文件:
g++ -g -Wall -std=c++11 main.cpp
g++
常用命令选项
选项 | 解释 |
---|---|
-ansi |
只支持 ANSI 标准的 C 语法. 这一选项将禁止 GNU C 的某些特色, 例如 asm 或 typeof 关键词. |
-c |
只编译并生成目标文件. |
-DMACRO |
以字符串"1"定义 MACRO 宏. |
-DMACRO=DEFN |
以字符串"DEFN"定义 MACRO 宏. |
-E |
只运行 C 预编译器. |
-g |
生成调试信息. GNU 调试器可利用该信息. |
-IDIRECTORY |
指定额外的头文件搜索路径DIRECTORY. |
-LDIRECTORY |
指定额外的函数库搜索路径DIRECTORY. |
-lLIBRARY |
连接时搜索指定的函数库LIBRARY. |
-m486 |
针对 486 进行代码优化. |
-o |
FILE 生成指定的输出文件. 用在生成可执行文件时. |
-O0 |
不进行优化处理. |
-O |
或 -O1 优化生成代码. |
-O2 |
进一步优化. |
-O3 |
比 -O2 更进一步优化, 包括 inline 函数. |
-shared |
生成共享目标文件. 通常用在建立共享库时. |
-static |
禁止使用共享连接. |
-UMACRO |
取消对 MACRO 宏的定义. |
-w |
不生成任何警告信息 |
-Wall |
生成所有警告信息 |
C++
程序可以定义为对象的集合, 这些对象通过调用彼此的方法进行交互.
现在让我们简要地看一下什么是类, 对象, 方法, 即时变量.
- 对象 - 对象具有状态和行为. 例如:一只狗的状态 - 颜色, 名称, 品种, 行为 - 摇动, 叫唤, 吃. 对象是类的实例.
- 类 - 类可以定义为描述对象行为/状态的模板/蓝图.
- 方法 - 从基本上说, 一个方法表示一种行为. 一个类可以包含多个方法. 可以在方法中写入逻辑, 操作数据以及执行所有的动作.
- 即时变量 - 每个对象都有其独特的即时变量. 对象的状态是由这些即时变量的值创建的.
让我们看一段简单的代码, 可以输出单词 Hello World
.
实例
#include <iostream>
using namespace std;
// main() 是程序开始执行的地方
int main()
{
cout << "Hello World"; // 输出 Hello World
return 0;
}
接下来我们讲解一下上面这段程序:
- C++ 语言定义了一些头文件, 这些头文件包含了程序中必需的或有用的信息. 上面这段程序中, 包含了头文件
<iostream>
. - 下一行
using namespace std
; 告诉编译器使用std
命名空间. 命名空间是C++
中一个相对新的概念. - 下一行
// main()
是程序开始执行的地方 是一个单行注释. 单行注释以//
开头, 在行末结束. - 下一行
int main()
是主函数, 程序从这里开始执行. - 下一行
cout << "Hello World"
; 会在屏幕上显示消息 "Hello World
". - 下一行
return 0
; 终止main( )
函数, 并向调用进程返回值0
.
接下来让我们看看如何把源代码保存在一个文件中, 以及如何编译并运行它. 下面是简单的步骤:
- 打开一个文本编辑器, 添加上述代码.
- 保存文件为
hello.cpp
. - 打开命令提示符, 进入到保存文件所在的目录.
- 键入 '
g++ hello.cpp
', 输入回车, 编译代码. 如果代码中没有错误, 命令提示符会跳到下一行, 并生成a.out
可执行文件. - 现在, 键入
a.out
来运行程序. - 您可以看到屏幕上显示 '
Hello World
'.
$ g++ hello.cpp
"nothing"
$ ./a.out
Hello World
请确保您的路径中已包含 g++
编译器, 并确保在包含源文件 hello.cpp
的目录中运行它. 您也可以使用 makefile
来编译 C/C++
程序.
在 C++
中, 分号是语句结束符. 也就是说, 每个语句必须以分号结束. 它表明一个逻辑实体的结束.
例如, 下面是三个不同的语句:
x = y;
y = y+1;
add(x, y);
语句块是一组使用大括号括起来的按逻辑连接的语句. 例如:
{
cout << "Hello World"; // 输出 Hello World
return 0;
}
C++
不以行末作为结束符的标识, 因此, 您可以在一行上放置多个语句. 例如:
x = y;
y = y+1;
add(x, y);
等同于
x = y; y = y+1; add(x, y);
C++
标识符是用来标识变量, 函数, 类, 模块, 或任何其他用户自定义项目的名称.
一个标识符以字母 A-Z
或 a-z
或下划线 _
开始, 后跟零个或多个字母, 下划线和数字(0-9
).
C++
标识符内不允许出现标点字符, 比如 @
, &
和 %
. C++
是区分大小写的编程语言.
因此, 在 C++
中, Manpower
和 manpower
是两个不同的标识符.
下面列出几个有效的标识符:
mohd zara abc move_name a_123
myname50 _temp j a23b9 retVal
下表列出了 C++
中的保留字. 这些保留字不能作为常量名, 变量名或其他标识符名称.
asm | else | new | this |
auto | enum | operator | throw |
bool | explicit | private | true |
break | export | protected | try |
case | extern | public | typedef |
catch | false | register | typeid |
char | float | reinterpret_cast | typename |
class | for | return | union |
const | friend | short | unsigned |
const_cast | goto | signed | using |
continue | if | sizeof | virtual |
default | inline | static | void |
delete | int | static_cast | volatile |
do | long | struct | wchar_t |
double | mutable | switch | while |
dynamic_cast | namespace | templat |
完整关键字介绍可查阅:C++ 的关键字(保留字)完整介绍
逗号,
可以用来连接两个表达式, 两个表达式依次计算. 例如
a=15,a*4;
<
,<=
,>
, >=
: 优先级高.
==
,!=
: 优先级低.
!
,&&
,||
条件运算符:表达式1 ? 表达式2 : 表达式3
, 优先级高于赋值运算符, 低于逻辑运算符.
x = a>b?a:b
三字符组就是用于表示另一个字符的三个字符序列, 又称为三字符序列. 三字符序列总是以两个问号开头.
三字符序列不太常见, 但 C++
标准允许把某些字符指定为三字符序列. 以前为了表示键盘上没有的字符, 这是必不可少的一种方法.
三字符序列可以出现在任何地方, 包括字符串
, 字符序列
, 注释
和预处理指令
.
下面列出了最常用的三字符序列:
三字符组 | 替换 |
---|---|
??= |
# |
??/ |
\ |
??' |
^ |
??( |
[ |
??) |
] |
??! |
` |
??< |
{ |
??> |
} |
??- |
~ |
如果希望在源程序中有两个连续的问号, 且不希望被预处理器替换, 这种情况出现在字符常量, 字符串字面值或者是程序注释中,
可选办法是用字符串的自动连接:"...?""?..."
或者转义序列:"...?\?..."
.
从Microsoft Visual C++ 2010
版开始, 该编译器默认不再自动替换三字符组.
如果需要使用三字符组替换(如为了兼容古老的软件代码), 需要设置编译器命令行选项/Zc:trigraphs
g++
仍默认支持三字符组, 但会给出编译警告.
只包含空格的行, 被称为空白行, 可能带有注释, C++
编译器会完全忽略它.
在 C++
中, 空格用于描述空白符
, 制表符
, 换行符
和注释
.
空格分隔语句的各个部分, 让编译器能识别语句中的某个元素(比如 int
)在哪里结束, 下一个元素在哪里开始. 因此, 在下面的语句中:
int age;
在这里, int
和 age
之间必须至少有一个空格字符
(通常是一个空白符
), 这样编译器才能够区分它们. 另一方面, 在下面的语句中:
fruit = apples + oranges; // 获取水果的总数
fruit
和 =
, 或者 =
和 apples
之间的空格字符不是必需的, 但是为了增强可读性, 您可以根据需要适当增加一些空格.
程序的注释是解释性语句, 您可以在 C++
代码中包含注释, 这将提高源代码的可读性.
所有的编程语言都允许某种形式的注释.
C++
支持单行注释和多行注释. 注释中的所有字符会被 C++
编译器忽略.
C++
注释以 /*
开始, 以 */
终止. 例如:
/* 这是注释 */
/* C++ 注释也可以
* 跨行
*/
注释也能以 //
开始, 直到行末为止. 例如:
实例
#include <iostream>
using namespace std;
int main()
{
cout << "Hello World"; // 输出 Hello World
return 0;
}
当上面的代码被编译时, 编译器会忽略 //
输出 Hello World
, 最后会产生以下结果:
Hello World
在 /*
和 */
注释内部, //
字符没有特殊的含义. 在 //
注释内, /*
和 */
字符也没有特殊的含义. 因此, 您可以在一种注释内嵌套另一种注释. 例如:
/* 用于输出 Hello World 的注释
cout << "Hello World"; // 输出 Hello World
*/
使用编程语言进行编程时, 需要用到各种变量来存储各种信息. 变量保留的是它所存储的值的内存位置. 这意味着, 当您创建一个变量时, 就会在内存中保留一些空间.
您可能需要存储各种数据类型(比如字符型
, 宽字符型
, 整型
, 浮点型
, 双浮点型
, 布尔型
等)的信息, 操作系统会根据变量的数据类型, 来分配内存和决定在保留内存中存储什么.
C++
为程序员提供了种类丰富的内置数据类型和用户自定义的数据类型. 下表列出了七种基本的 C++
数据类型:
类型 | 关键字 |
---|---|
布尔型 | bool |
字符型 | char |
整型 | int |
浮点型 | float |
双浮点型 | double |
无类型 | void |
宽字符型 | wchar_t |
其实 wchar_t
是这样来的:
typedef short int wchar_t;
所以 wchar_t
实际上的空间是和 short int
一样.
一些基本类型可以使用一个或多个类型修饰符进行修饰:
signed
unsigned
short
long
下表显示了各种变量类型在内存中存储值时需要占用的内存, 以及该类型的变量所能存储的最大值和最小值.
注意:不同系统会有所差异.
类型 | 位 | 范围 |
---|---|---|
char |
1个字节 | -128到127 或者 0到255 |
unsigned char |
1 个字节 | 0到255 |
signed char |
1个字节 | -128到127 |
int |
4个字节 | -2147483648到2147483647 |
unsigned int |
4个字节 | 0到4294967295 |
signed int |
4个字节 | -2147483648到2147483647 |
short int |
2个字节 | -32768到32767 |
unsigned short int |
2个字节 | 0到65,535 |
signed short int |
2个字节 | -32768到32767 |
long int |
8个字节 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
signed long int |
8个字节 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
unsigned long int |
8个字节 | 0到18,446,744,073,709,551,615 |
float |
4个字节 | 精度型占4个字节(32位)内存空间, +/- 3.4e +/- 38 (~7 个数字) |
double |
8个字节 | 双精度型占8个字节(64位)内存空间, +/- 1.7e +/- 308 (~15 个数字) |
long double |
16个字节 | 长双精度型16个字节(128位)内存空间, 可提供18-19位有效数字. |
wchar_t |
2或4个字节 | 1个宽字符 |
从上表可得知, 变量的大小会根据编译器和所使用的电脑而有所不同. 下面实例会输出您电脑上各种数据类型的大小.
#include<iostream>
using namespace std;
int main()
{
cout << "type: \t\t" << "************size**************"<< endl;
cout << "bool: \t\t" << "所占字节数:" << sizeof(bool);
cout << "\t最大值:" << (numeric_limits<bool>::max)();
cout << "\t\t最小值:" << (numeric_limits<bool>::min)() << endl;
cout << "char: \t\t" << "所占字节数:" << sizeof(char);
cout << "\t最大值:" << (numeric_limits<char>::max)();
cout << "\t\t最小值:" << (numeric_limits<char>::min)() << endl;
cout << "signed char: \t" << "所占字节数:" << sizeof(signed char);
cout << "\t最大值:" << (numeric_limits<signed char>::max)();
cout << "\t\t最小值:" << (numeric_limits<signed char>::min)() << endl;
cout << "unsigned char: \t" << "所占字节数:" << sizeof(unsigned char);
cout << "\t最大值:" << (numeric_limits<unsigned char>::max)();
cout << "\t\t最小值:" << (numeric_limits<unsigned char>::min)() << endl;
cout << "wchar_t: \t" << "所占字节数:" << sizeof(wchar_t);
cout << "\t最大值:" << (numeric_limits<wchar_t>::max)();
cout << "\t\t最小值:" << (numeric_limits<wchar_t>::min)() << endl;
cout << "short: \t\t" << "所占字节数:" << sizeof(short);
cout << "\t最大值:" << (numeric_limits<short>::max)();
cout << "\t\t最小值:" << (numeric_limits<short>::min)() << endl;
cout << "int: \t\t" << "所占字节数:" << sizeof(int);
cout << "\t最大值:" << (numeric_limits<int>::max)();
cout << "\t最小值:" << (numeric_limits<int>::min)() << endl;
cout << "unsigned: \t" << "所占字节数:" << sizeof(unsigned);
cout << "\t最大值:" << (numeric_limits<unsigned>::max)();
cout << "\t最小值:" << (numeric_limits<unsigned>::min)() << endl;
cout << "long: \t\t" << "所占字节数:" << sizeof(long);
cout << "\t最大值:" << (numeric_limits<long>::max)();
cout << "\t最小值:" << (numeric_limits<long>::min)() << endl;
cout << "unsigned long: \t" << "所占字节数:" << sizeof(unsigned long);
cout << "\t最大值:" << (numeric_limits<unsigned long>::max)();
cout << "\t最小值:" << (numeric_limits<unsigned long>::min)() << endl;
cout << "double: \t" << "所占字节数:" << sizeof(double);
cout << "\t最大值:" << (numeric_limits<double>::max)();
cout << "\t最小值:" << (numeric_limits<double>::min)() << endl;
cout << "long double: \t" << "所占字节数:" << sizeof(long double);
cout << "\t最大值:" << (numeric_limits<long double>::max)();
cout << "\t最小值:" << (numeric_limits<long double>::min)() << endl;
cout << "float: \t\t" << "所占字节数:" << sizeof(float);
cout << "\t最大值:" << (numeric_limits<float>::max)();
cout << "\t最小值:" << (numeric_limits<float>::min)() << endl;
cout << "size_t: \t" << "所占字节数:" << sizeof(size_t);
cout << "\t最大值:" << (numeric_limits<size_t>::max)();
cout << "\t最小值:" << (numeric_limits<size_t>::min)() << endl;
cout << "string: \t" << "所占字节数:" << sizeof(string) << endl;
// << "\t最大值:" << (numeric_limits<string>::max)() << "\t最小值:" << (numeric_limits<string>::min)() << endl;
cout << "type: \t\t" << "************size**************"<< endl;
return 0;
}
本实例使用了endl
, 这将在每一行后插入一个换行符, <<
运算符用于向屏幕传多个值.
我们也使用 sizeof()
函数来获取各种数据类型的大小.
当上面的代码被编译和执行时, 它会产生以下的结果, 结果会根据所使用的计算机而有所不同:
type: ************size**************
bool: 所占字节数:1 最大值:1 最小值:0
char: 所占字节数:1 最大值: 最小值:?
...
10
进制转R
进制:除R
取余数, 位数依次升高.
10
进制小数转R
进制:乘R
取整数部分, 小数部分再乘R
取整数迭代, 位数依次降低.
R
进制转10
进制:科学计数法, 级数求和.
整数的几种编码:
- 原码:以
0
表示+
号,1
表示-
号, 但是这样0
的表示不唯一, 且正负号需要单独的运算规则 - 模数:以钟表为例,
12
就是模数. - 补数:一个数减去另一个数, 等于第一个数加上第二个数的补数. 例如:
8(-2)=8+10(mod 12)=6
- 反码:对于负整数:原码符号位不变(仍是
1
), 其余各位取反;对于正整数, 原码就是补码. 反码是求补码的中间码. - 补码:补码=
反码的最低位+1
整数使用补码的优点是:
0
的表示唯一- 符号位可作为数值参与运算
- 补码运算的结果仍为补码
- 补码再求补即可得到原码
如果负数之和得正数, 或正数之和得负数, 说明运算结果溢出
.
小数的两种表示方法:定点方案, 与浮点方案. 现在一般都用浮点数, 也就是小数点位置可变.
E
:2
的幂次, 称为数字N
的阶码, 反映了该浮点数表示的数据范围.
M
:N
的尾数, 其位数反映了数据的精度.
字符在计算机中通过编码表示:西文常用ASCII
码, 7
位二进制数表示一个字符, 最多为2^7=128
个.
汉字编码:中国国家标准:GB 18030-2005 信息技术中文编码字符集
布尔类型, 默认转换, 非零数据转换成false
, 其他数值转换成true
.
cout << 表达式 << 表达式 ... cin >> 表达式 >> 表达式...
I/O
流类库操纵符.
dec
: 十进制表示hex
: 十六进制表示oct
: 八进制表示ws
: 提取空白符endl
: 插入换行符, 并刷新流ends
: 插入空字符setprecision(int)
: 设置浮点数的小数位数(包括小数点)setw(int)
: 设置域宽
例如:
cout << setw(5) << setprecision(3) << 3.1415 ;
if (表达式1)
{...}
else if (表达式2)
{...}
else if
注意if
和else
的匹配关系:
if (表达式1)
{
if () 语句1
}
else
switch
语句: 没有break
不会默认跳出, 每个 case
都应该包含break
; case
包含多个语句, 无需{}
, 因为case, break
就相当于括号.
表达式, 判断值都是int
or char
类型.
switch (表达式){
case 判断值0: xxx; break;
case 判断值1: xxx; break;
...
default:
}
int i=1, sum=0;
while (i<=10) {
sum +=i;
i++;
}
do while
会先执行一次循环体. 而while
先判断.
do {
语句
}
while (判断)
for
语句明确控制循环次数. for( 初始语句;循环体;循环后语句)
for (int k=1; k<=n; k++){
语句
}
for
语句中的范围for
形式, 用于遍历一个容器中的序列:
for (声明: 表达式)
语句
break
: 跳出最内层的循环体continue
: 提前结束本次循环, 进入下一次.goto
: 跳转到任意地方.
给类型起一个别名
typedef 已有类型名 新类型名表 //c 语言继承的办法
// 或者
using 新类型名 = 已有类型名
using Area = double
enum 枚举类型名 {变量值列表}
enum Weekday {SUN,MON,TUE,WED,THU,FRI,SAT}
也可以定义限定类型的枚举类型. 将整数值赋值给枚举类型要进行强制类型转换.
auto
: 编译器通过初始值自动推断变量的类型. 例如:auto val=val1+ val2
.
如果都是int
, 则val
is int
. 如果有一个double
, 则val
是doule
类型.
decltype(cls) j =2
:表示j
的初始值为2
, 但类型与cls
一致.
struct
把一组相互关联的数据整合在一起. 例如:
struct MyTimeStruct
{
unsigned int year;
unsigned int month;
unsigned int day;
}
// 赋值和调用
myTime={2015,3,16}
myTime.day
- 内联函数
constexpr
函数- 带默认参数值的函数
- 函数重载: 编译时绑定--早绑定, 晚绑定
- C++ 系统函数
函数定义的语法形式
类型标识符 函数名(形式参数表)
{
语句序列
return x // void 函数不需要写 return
}
形式参数的形式为: type1 name1, ....
调用函数需要声明函数原型
: 类型标识符 被调函数名 (含类型说明的形参表), 调用使用函数名(实参列表)
.
调用堆栈
随机函数:rand()
, srand()
.
函数的嵌套调用和递归调用.
函数被调用时才分配形参的存储单元 实参可以是常量, 变量或表达式 实参类型必须与形参相符 值传递是传递参数值, 即单向传递 引用传递可以实现双向传递 常数引用作参数可以保障实参数据的安全
引用(&
)是标识符的别名.
定义引用时, 必须同时对它进行初始化, 指向一个已存在的对象. 例如
int i,j;
int &ri = i; //定义 int 引用 ri, 初始化为变量i的引用
j=10
ri=j; // 相当于 i=j
一旦引用被初始化后, 就不能改为指向其他对象. 引用可以作为形参.
类模板: initializer_list
initializer_list ls; //initializer_list 的元素类型是 string initializer_list ls; //initializer_list 的元素类型是 int
initializer_list
比较特殊的是, 其对象中的元素永远是常量值, 无法改变initializer_list
对象中元素的值.
含有initializer_list
形参的函数也可以同时拥有形参.
编译器实现的, 没有调用子函数开销. 声明内联函数用关键字inline
. 编译时用函数体进行替换, 节省了参数传递, 控制转移等开销.
内联函数不能有循环语句和switch
语句. 必须出现在第一次调用之前, 不能使用异常接口声明.
inline
只是建议, 编译器不一定理会.
constexpr
修饰的函数, 在其所有参数都是constexpr
时, 一定返回constexpr
.例如
constexpr int get_size(){return 20;}
constexpr int foo = get_size(); \\正确: foo 是一个常量表达式
编译期间可计算函数.
int add(int x=5,int y=6){
return x+y;
}
int main(){
add(10,20); //10+20
add(10); // 10+6
add(); //5+6
}
带有默认值的形参必须在形参列表的最右, 即默认参数的右边不能有无默认值的参数;
调用时实参与形参的结合次序是从左到右.
```cpp
int add(int x,int y=6, int z=6);
如果函数声明在前, 则在声明中给定默认值; 如果函数定义在前, 则在定义中给定默认值;
在编译的时候实现, 利用静态的多态性实现. 允许功能相似的函数在相同的作用域内以相同函数名声明, 从而形成重载. 方便使用与记忆.
int add (int x, int y);
float add(float add, float y);
int add (int x, int y);
int add (int x, int y, int z);
使用系统函数要包含相应的头文件. 如cmath
.
构造函数, 析构函数
抽象, 封装, 继承, 多态.
封装: 只通过外部接口调用, 以特定的访问权限.
实现封装: 类声明中的{}
多态: 同一名称, 不同的功能实现方式. 目的: 减少程序标识符的个数.
设计类
就是设计类型
.
class 类名称
{
public:
公有成员(外部接口)
private:
私有成员
protected:
保护成员
}
紧跟在类名称后面声明私有成员, 则关键字private
可以省略.
类名 对象名; // 例如
Clock myClock
类中的成员可以占直接互相访问, 外部要访问使用对象名.成员名
.
类中的函数可以在类外使用类::函数名
定义函数体. 比较简单的函数, 可以声明为内联成员函数.
内联函数体中不要有复杂结构(如循环语句和switch
语句)
- 直接把函数体放在类的声明中
- 在类外使用
inline
关键字声明.
类中的特殊函数, 用于描述初始化算法. 在对象被创建时使用特定的值构建对象, 将对象初始化为一个特定的初始状态.
构造函数的形式:
- 函数名与类名相同
- 不能定义返回值类型, 也不能有
return
语句. - 可以有形式参数, 也可以没有形式参数
- 可以是内联函数
- 可以重载
- 可以带默认参数值
构造函数在创建对象时, 自动被调用. 默认构造函数要么不需要实参, 要么全部参数带有默认值. default constructor.
Clock(); //这两个构造函数不能同时出现.
Clock(int newH...);
隐含生成的构造函数: 如果程序未定义构造函数, 编译器将自动生成一个默认构造函数.
- 参数表为空, 不为数据成员设置初始值.
- 如果类内定义了成员的初始值, 则使用内类定义的初始值.
- 如果没有定义类内的初始值, 则以默认方式初始化.
- 基本类型的数据默认初始化是不确定的.
可以使用Clock()=default
强制编译器生成默认构造函数.
构造函数可以有多个重载形式.
委托构造函数使用类的其他构造函数执行初始化过程. 例如:
Clock(int newH, int newM, int newS):hour(newH),minute(newM),second(newS){}
Clock():Clock(0,0,0){} //默认构造函数可以调用带参数的构造函数.
使用对象A
给对象B
初始化, 需要用到复制构造函数.
class 类名{
public: 类名(形参); //初始化构造函数
类名(const 类名 &对象名);// 复制构造函数, const 是为了保证常引用, 单向引用, 保持安全性
//...
};
//...
类名::类(const 类名 &对象名)//复制构造函数的实现
{ 函数体 }
调用复制构造函数的情况:
- 对象
A
初始化对象B
- 函数的形参是类的对象, 形实结合使用复制构造.
- 如果函数的返回值是类的对象, 函数执行完返回一个临时无名对象, 传递给主调函数, 也发生复制构造.
如果没有定义, 则编译器产生隐含的复制构造函数. 将两个对象的数据成员一一对应.
如果不希望对象被复制构造.
c++98:将复制函数声明为private, 并且不提供函数实现.
c++11:用=delete
提示编译器不生成默认复制构造函数.
class Point{
public:
Point(int xx=0,int yy=0){x=xx; y=yy;}
Point(const Point& p)=delete;// 提示编译器不生成默认复制构造函数
private:
int x,y;//私有数据
};
int main(){
Point a; // 第一个对象A
Point b(a); // 情况一, 用A初始化B, 调用拷贝构造函数
cout <<b.getX() <<endl; //情况二, 对象B作为fun1的实参, 调用拷贝构造函数
fun1(b); // 情况三, 函数的返回值是类对象, 函数返回时调用拷贝构造函数
b=fun2();
cout << b.getX()<<endl;
return 0;
}
当一个对象被调用时, 会自动调用构造函数. 当一个对象消亡, 会自动调用析构函数. 析构函数完成对象被删除前的一些清理工作.
比如函数中的局部对象, 在函数调用结束时要消亡. 在对象的生存周期结束的时刻, 系统自动调用析构函数. 如果程序中未声明析构函数, 编译器将自动产生一个析构函数, 其函数体为空.
析构函数的原型:~类名()
;
析构函数没有参数, 没有返回类型, 没有return
语句.
在main
函数return
之前, 要将所有存活的对象析构.
类中的成员是另一个类的对象---部件类. 可以在已有抽象的基础上实现更复杂的抽象.
类组合的构造函数设计:不仅要负责本类中的基本类型成员的初始化, 还要将对象成员初始化.
声明形式:
类名::类名(对象成员所需的形参, 本类成员形参):对象1(参数), 对象2(参数), ...
{// 函数体其他语句}
成员按照在类体中声明的次序初始化, 而不是按照初始化列表
的给出次序.
首先对构造函数
初始化列表中列出的成员:基本成员和对象
成员, 进行初始化, 初始化次序是成员在类体中定义的次序.
对象成员
构造函数的调用次序: 按对象成员的定义顺序, 先声明者先构造.
初始化列表
中未出现的成员对象, 调用默认构造函数(无形参)初始化.
处理完初始化列表之后, 再执行构造函数的函数体.
形实参结合的时候, 是从右边往左边传递的, 先结合右边.
两个类中的函数, 相互引用对方的类名.
类应该先声明, 后使用. 如果需要在某个类声明之前, 就引用该类, 则应该进行引用声明. 前向引用声明只为程序引入一个标识符, 类体可以在其他地方声明.
calss B; //前向引用声明
class A{
public:
void f(B b);
};
class B{
public:
void g(A a);
}
前向引用声明注意事项:
- 在提供一个完整的类声明之前, 不能声明该类的对象, 也不能在内联函数成员中使用该类的对象.
- 当使用前向引用声明时, 只能使用被声明的符号, 而不能涉及类的任何细节.
class Fred; //前向引用声明
class Barney{
Fred x; //错误:类 Fred 的声明尚不完善,无法创建对象.
};
class Fred{
Barney y;
};
UML
是可视化的面向对象的建模语言.
UML
有三个基本的部分:
- 事物(Things)
- 关系(Relationships)
- 图(Diagrams)
类图举例:
Clock
--------
- hour: int
- minute:int
- second:int
+ showTime();void
+ setTime(newH:int=0,newM:int=0,newS:int=0);void
对象图
myClock : Clock
--------
- hour: int
- minute:int
- second:int
依赖关系
类A---->类B
- 关联作用.
- 包含关系--聚集: 共享聚集: 部分可以参加多个整体.比如窗体和输入法. 组成聚集:整体拥有各个部分,整体与部分共存, 如果整体不存在了, 部分也不存在.如窗体和按钮
- 继承关系--泛化: 子类继承父类.
结构体是一种特殊形态的类. 它与类的唯一区别是:类的缺省访问是priavte
, 结构体的缺省访问权限是public
.
什么时候用结构体?
定义主要用来保存数据, 而没有什么操作.
人们习惯将结构体的数据成员设为公有, 因此用结构体更方便.
主要为了兼容C
.
struct 结构体名称{
公有成员
protected:
保护型成员
private:
私有成员
}
结构体中可以有数据成员和函数成员, C
只有数据成员.
- 如果一个结构体的全部数据成员都是公共成员
- 没有用户定义的
构造函数
. - 没有基类和虚函数
这个结构体可以用下面的语法形式初始化:
类型名 变量名 = {成员数据1初值,成员数据2初值,....}
例如用结构体表示学生的基本信息.
#include <iostream>
#include<iomanip>
#include<string>
using namespace std;
struct Student
{//学生信息结构体
int num; //学号
string name; //姓名, 字符串对象
char sex; //性别
int age; //年龄
};
int main(){
Student stu={97001,"Lin Lin",'F',19}
cout<<"Num: "<<stu.num<<endl;
cout<<"Name: "<<stu.name<<endl;
cout<<"Sex: "<<stu.sex<<endl;
cout<<"Age: "<<stu.age<<endl;
return 0;
}
不常用的数据类型, 联合体的目的是存储空间的共用. 定义形式:
union 联合体名称{
公有成员
protected:
保护型成员
private:
私有成员
};
特点:
- 成员共用
同一组
内存单元. - 任何两个成员不会同时有效.
union Mark{ //表示成绩的联合体
char grade; //等级制的乘积
bool pass; //只记是否通过课程的乘积
int percent; //百分制的乘积.
};
如果int percent
占四个bit
,则 Mark
占用4
个字节, 即按照最长的乘员类型存储.
还可以定义无名联合体, 它们共用内存.
union{
int i; float f;
}
i =10; //在程序中使用
f=2.2;
枚举类也称为强类型枚举. 之前的枚举元素只能是整数的子集.
枚举类定义:
enum class 枚举类型名: 底层类型{枚举值列表}; //默认的底层类型是 int 类型
//例如:
enum class Type {General, Light, Medium,Heavy};
enum class Type:char{General, Light, Medium,Heavy};
enum class Category {General=1, Pistol,MachineGun,Cannon}; //可以指定枚举常量的值, 后面依次递增.
枚举类的优势:
- 强作用域:其作用域限制在枚举类中. 使用时应该使用
Type::General
,所以枚举值可以重名. - 转换限制: 枚举类对象不可以与整型隐式地互相转换.
- 可以指定底层类型: 如
enum class Type:char{Light, Medium,Heavy};
#include<iostream>
using namespace std;
enum class Side{Right,Left};
enum class Thing{Wrong,Right}; //不冲突
int main{
Side s=Side::Right;
Thing w=Thing::Wrong;
cout <<(s ==w) <<endl; //编译错误, 无法直接不同枚举类.
return 0;
}
- 属于类的成员:静态数据成员
- 用于处理静态数据成员的函数:静态成员函数.
友元: 对一些类外的函数, 其他的类, 给予授权, 使之可以访问类的私有成员.
为了安全, 可以用const
修改.
- 函数原型作用域: 函数原型中的参数, 也就是形参表的括号之内, 写不写名字都可以.
- 局部作用域
- 类作用域
- 文件作用域
- 命名空间作用域
函数原型作用域
double area(double radius);
局部作用域, 或者叫块作用域.
- 函数的形参, 在块中声明的标识符;用
{}
创建作用域. - 作用域自声明处起, 限于块中.
类作用域:
类的成员具有类作用域, 其范围包括类体和成员函数体.
在类作用域以外访问类的成员:
静态成员:通过类名, 或者该类的对象名, 对象引用访问. 例如对象.属性
非静态成员:通过类名, 或者该类的对象名, 对象引用, 对象指针访问.
文件作用域: 不在前述各个作用域中出现的声明, 就具有文件作用域; 其作用域开始于声明点, 结束于文件尾;
即使一个标识符在作用域之内, 也不一定是可见的. 可见性从引用角度来说, 表示从内层作用域向外层作用域“看”时能看见什么. 如果标识符在某处可见, 就可以引用.
如果标识符在外层中声明, 且在内层中没有被同名标识符覆盖, 则可见. 如果重名, 则不可见.
C++程序中, 对象和变量的生存期分为静态生存期和动态生存期.
静态生存期
- 静态生存期与程序的运行期相同.
- 在文件作用域中生命的对象具有这种周期.
- 在函数内部声明静态生存期对象, 要冠以关键字
static
动态生存期
开始于程序执行到声明点时, 结束于最近的作用域结束时.
块作用域中声明的, 没有用static
修饰的对象, 是动态生存期的对象(习惯上称为局部生存期对象).
- 用关键字
static
声明, - 为该类的所有对象共享, 静态数据成员具有静态生存周期
- 必须在类外定义和初始化, 用
::
来指明所属的类.
静态函数成员用于处理静态数据. 静态函数不知道自己被哪个具体对象实例调用. 它用来处理类的公有静态成员.
对象的一般成员函数, 即非静态函数, 通常隐含了用指针传入一个this
指针, 指向对象本身.
- 友元是
C++
提供的一种破坏数据封装和数据隐藏的机制. - 通过将一个模块声明为另一个模块的友元, 一个模块能够引用到另一个模块中本该被隐藏的信息.
- 可以声明友元函数和友元类.
- 为了确保数据的完整性, 及数据封装与隐藏的原则, 建议慎用友元.
友元函数是在类声明中由关键字friend
修饰说明的非成员函数, 在它的函数体重能够通过对象名访问pravite
和protected
成员.
作用:增加灵活性, 封装和效率的平衡.
访问对象中的成员需要通过对象名.
- 若一个类为另一个类的友元, 则此类的所有成员都能访问对方类的私有成员.
- 声明语法:将友元类名在另一个类中使用
friend
修饰说明.
友元关系是单向的, 声明B
类是A
类的友元!=A
类是B
类的友元.
常对象
:必须进行初始化, 不能被更新. 专门用来处理常对象的函数称为常函数.
const 类名 对象名
常成员: 用const
进行修饰的类成员:常数据成员和常函数成员
常引用
:被引用的对象不能被更新.
const 类型说明符 &引用名;
常数组
: 数组元素不能被更新
类型说明符 const 数组名[大小]
常指针
: 指向常量的指针
常成员函数
:
- 使用
const
关键字说明的函数. - 常成员函数不更新对象的数据成员.
- 常成员函数说明格式:
类型说明符 函数名(参数表) const;
- 这里,
const
是函数类型的一个组成部分, 因此在实现部分也要带const
关键字. const
关键字可以被用于参与对重载函数的区分- 通过
常对象
只能调用它的常成员函数
. - 普通对象也可以调用常函数, 所以常函数可以主动声明为
const
.
在友元函数中用常引用作参数, 既能获得较高的执行效率, 又能保证实参的安全性.
c++
程序的一般组织结构
一个工程文件可以划分为多个源文件, 例如:
- 类声明文件(
.h
文件) - 类实现文件(
.cpp
文件) - 类的使用文件(
main()
所在的.cpp
文件)
利用工程来组合各个文件.
自己写的头文件通常带.h
, 调用时用inclue "xxx.h"
用"
括起来的, 默认会在当前目录寻找.
而用<iostream>
括起来的, 会到安装的默认目录去找, 且没有后缀h
.
实例的程序可以用如下命令编译:
g++ -lstdc++ Point.h Point.cpp 5_10.cpp -o test
- 一个变量, 除了在定义它的源文件中可以使用, 还能被其他文件使用.
- 文件作用域中定义的变量, 默认情况下都是外部变量.
- 如果需要在其他文件中使用, 需要用
extern
关键字声明.
- 在所有类之外声明的函数(也就是非成员函数), 都是具有文件作用域的.
- 这样的函数都可以在不同的编译单元中被调用.
- 只要在调用之前进行引用性声明(即声明函数原型)即可.
在匿名命名空间中定义的变量和函数, 都不会暴露给其他的编译单元. 语法形式为:
namespace { //匿名的命名空间
int n;
void f() {n++;}
}
标准c++库是一个极为灵活并可扩展的可重用软件模块的集合. 标准c++类与组件在逻辑上分为6种类型:
- 输入/输出类, iostream
- 容器类与抽象数据类型, 类模板,数据操作.
- 存储管理类,
- 算法.查找, 排序等等.
- 错误支持
- 运行环境支持
-
#include
包含指令:将一个源文件嵌入到当前源文件中该点处.#include<文件名>
:按标准方式搜索, 文件位于c++
系统目录的include
子目录下.#include"文件名"
: 首先在当前目录中搜索, 若没有, 再按标准方式搜索.
-
#define
:宏定义指令.- 定义符号常量, 很多情况下已被
const
定义语句取代. - 定义带参数宏, 已被内联函数取代.
- 定义符号常量, 很多情况下已被
-
#undef
: 删除由#define
定义的宏, 使之不再起作用. -
条件编译指令——
#if
,#endif
,#ifdef
#if 常量表达式1
程序正文1 //当"常量表达式1" 非零时编译
#elif 常量表达式2
程序正文2 //当"常量表达式2" 非零时编译
#else
程序正文3 //其他情况下编译
#endif
如果"标识符"经#defined
定义过, 且未经#undef
删除, 则编译程序段1
;否则编译程序段2
#ifdef 标识符
程序段1
#else
程序段2
#endif
如果"标识符"未被#defined
定义过, 则编译程序段1
;否则编译程序段2
#ifndef 标识符
程序段1
#else
程序段2
#endif
可以这么使用:在头文件中设置标识符
, 如果标识符
未被定义过, 就编译某段程序, 并定义标识符
.
如果这段程序被多次包含, 就会避免重复编译这段程序.
这样多次include
时, 也不会重复定义函数和常量.