文章目录
一、基础语法
1.1 C++ 简介
- C++ 是一种静态类型的、编译式的、通用的、大小写敏感的、不规则的编程语言,支持过程化编程、面向对象编程和泛型编程。- C++ 是 C 的一个超集,事实上,任何合法的 C 程序都是合法的 C++ 程序。- 使用静态类型的编程语言是在编译时执行类型检查,而不是在运行时执行类型检查。
面向对象程序设计
- 完全支持面向对象的程序设计,包括面向对象开发的四大特性:封装、抽象、继承、多态。
标准库
- 核心语言,提供了所有构件块,包括变量、数据类型和常量,等等。1. C++ 标准库,提供了大量的函数,用于操作文件、字符串等。1. 标准模板库(STL),提供了大量的方法,用于操作数据结构等。
ANSI 标准
- 所有的C++编译器制造商均支持ANSI 标准,能够保证其便携性(代码在Mac、UNIX、Windows、Alpha 计算机上都能通过编译)。
1.2 C++ 环境设置
文本编辑器
编译器
- 写在源文件中的源代码是人类可读的源。它需要"编译",转为机器语言,这样 CPU 可以按给定指令执行程序。- 大多数的 C++ 编译器并不在乎源文件的扩展名,但是如果您未指定扩展名,则默认使用 .cpp。- 常用的免费可用的编译器是 GNU 的 C/C++ 编译器
1.3 C++ 基本语法
C++ 程序可以定义为对象的集合,这些对象通过调用彼此的方法进行交互。
- 对象 - 对象具有状态和行为,对象是类的实例。
- 类 - 类可以定义为描述对象行为/状态的 模板/蓝图。
- 方法 - 从基本上说,一个方法表示一种行为。一个类可以包含多个方法。可以在方法中写入逻辑、操作数据以及执行所有的动作。
- 即时变量 - 每个对象都有其独特的即时变量。对象的状态是由这些即时变量的值创建的。
C++ 程序结构
- Eg:输出“Hello World”
#include <iostream>
using namespace std;
// main() 是程序开始执行的地方
int main()
{
cout << "Hello World"; // 输出 Hello World
return 0;
}
using namespace std;告诉编译器使用 std 命名空间。
编译&执行 C++ 程序
- 上述代码保存为“hello.cpp”,打开所在路径,输出“Hello World”
$ g++ hello.cpp
$ ./a.out
C++ 中的分号 & 语句块
- 在 C++ 中,分号是语句结束符。也就是说,每个语句必须以分号结束。它表明一个逻辑实体的结束。
- 语句块是一组使用大括号括起来的按逻辑连接的语句。
1.4 C++ 注释
/*开始*/结束(多行)//单行
1.5 C++ 数据类型
基本内置类型
| 类型 | 关键字 |
|---|---|
| 布尔型 | bool |
| 整型 | int |
| 浮点型 | float |
| 双浮点型 | double |
| 无类型 | void |
| 宽字符型 | wchar_t |
typedef 声明
- 可以使用 typedef 为一个已有的类型取一个新的名字。
typedef type newname;
枚举类型
- 枚举类型(enumeration)是C++中的一种派生数据类型,它是由用户定义的若干枚举常量的集合。如果一个变量只有几种可能的值,可以定义为枚举(enumeration)类型。
- 关键字 enum
enum 枚举名{
标识符[=整型常数],
标识符[=整型常数],
...
标识符[=整型常数]
} 枚举变量;
- 如果枚举没有初始化, 即省掉"=整型常数"时, 则从第一个标识符开始。例如,下面的代码定义了一个颜色枚举,变量 c 的类型为 color。最后,c 被赋值为 “blue”。
enum color { red, green, blue } c;
c = blue;
- 默认情况下,第一个名称的值为 0,第二个名称的值为 1,第三个名称的值为 2,以此类推。但是,您也可以给名称赋予一个特殊的值,只需要添加一个初始值即可。例如,在下面的枚举中,green 的值为 5。blue 的值为 6,因为默认情况下,每个名称都会比它前面一个名称大 1,但 red 的值依然为 0。
enum color { red, green=5, blue };
1.6 C++ 变量类型
- 大写字母和小写字母是不同的,因为 C++ 是大小写敏感的。
C++ 中的变量声明
- 使用extern声明变量,使用多个文件且只在其中一个文件中定义变量。
- 变量可以声明多次,但只能定义一次。
- extern作用:extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。此外extern也可用来进行链接指定。
- 在文件t2.cpp中有代码:
int x = 10;
在文件t1.cpp中有代码:
#include <iostream>
using namespace std;
extern int x;
int main() {
cout << x;
return 0;
}
输出:10 extern表示声明一个变量,声明使得这个名字为程序所指,而定义创建了和这个名字相关联的实体。 如:
int x = 10;
则是定义一个变量。 声明和定义分开,这样的意义就是可以在一个文件中使用另一个文件的变量,如上面的t1.cpp使用t2.cpp的变量。
C++ 中的左值(Lvalues)和右值(Rvalues)
- 左值(lvalue):指向内存位置的表达式被称为左值(lvalue)表达式。左值可以出现在赋值号的左边或右边。
- 右值(rvalue):术语右值(rvalue)指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式,也就是说,右值可以出现在赋值号的右边,但不能出现在赋值号的左边。
- 变量是左值,数值型的数字是右值(Eg:10)
1.7 C++ 变量作用域
- 在函数或一个代码块内部声明的变量,称为局部变量。
- 在函数参数的定义中声明的变量,称为形式参数。
- 在所有函数外部声明的变量,称为全局变量。
- 当局部变量被定义时,系统不会对其初始化,必须自行对其初始化。定义全局变量时,系统会自动初始化。
1.8 C++ 常量
- 常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量。- 常量可以是任何的基本数据类型,可分为整型数字、浮点数字、字符、字符串和布尔值。- 常量就像是常规的变量,只不过常量的值在定义后不能进行修改。
- 可以带一个后缀,后缀是 U 和 L 的组合,U 表示无符号整数(unsigned),L 表示长整数(long)
定义常量
- 使用 #define 预处理器。
#define identifier value
// eg:
#define LENGTH 10
- 使用 const 关键字。
const type variable = value;
// eg:
const int LENGTH = 10;
- 二者区别: 用const的优点:会进行类型安全检查。而define没有安全检查,且可能会产生意料不到的错误。
1.9 C++ 修饰符类型
- C++ 允许在 char、int 和 double 数据类型前放置修饰符。- 数据类型修饰符:signed、unsigned、long、short
- 修饰符 signed、unsigned、long 和 short 可应用于整型,signed 和 unsigned 可应用于字符型,long 可应用于双精度型。
- 修饰符 signed 和 unsigned 也可以作为 long 或 short 修饰符的前缀。例如:unsigned long int。
类型限定符
- volatile作用: volatile关键字是防止在共享的空间发生读取的错误。只保证其可见性,不保证原子性;使用volatile指每次从内存中读取数据,而不是从编译器优化后的缓存中读取数据,简单来讲就是防止编译器优化。
1.10 C++ 存储类
存储类定义 C++ 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前。
- auto- register- static- extern
- mutable- thread_local (C++11)
- 从 C++ 11 开始,auto 关键字不再是 C++ 存储类说明符,且 register 关键字被弃用。
auto
- 自 C++ 11 以来,auto 关键字用于两种情况:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。
register
- register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)
- Eg:
register int miles; - 寄存器只用于需要快速访问的变量,比如计数器。还应注意的是,定义 ‘register’ 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制。
static
- static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。(局部静态变量不销毁)
- static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。
#include <iostream>
// 函数声明
void func(void);
static int count = 10; /* 全局变量 */
int main()
{
while(count--)
{
func();
}
return 0;
}
// 函数定义
void func( void )
{
static int i = 5; // 局部静态变量
i++;
std::cout << "变量 i 为 " << i ;
std::cout << " , 变量 count 为 " << count << std::endl;
}
结果:
变量 i 为 6 , 变量 count 为 9
变量 i 为 7 , 变量 count 为 8
变量 i 为 8 , 变量 count 为 7
变量 i 为 9 , 变量 count 为 6
变量 i 为 10 , 变量 count 为 5
变量 i 为 11 , 变量 count 为 4
变量 i 为 12 , 变量 count 为 3
变量 i 为 13 , 变量 count 为 2
变量 i 为 14 , 变量 count 为 1
变量 i 为 15 , 变量 count 为 0
extern
- extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。
- extern 是用来在另一个文件中声明一个全局变量或函数,通常用于当有两个或多个文件共享相同的全局变量或函数。
mutable
- 允许对象的成员替代常量。也就是说,mutable 成员可以通过 const 成员函数修改。
thread_local
- 使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。
- 可以将 thread_local 仅应用于数据声明和定义,thread_local 不能用于函数声明或定义。
thread_local int x; // 命名空间下的全局变量
class X
{
static thread_local std::string s; // 类的static成员变量
};
static thread_local std::string X::s; // X::s 是需要定义的
void foo()
{
thread_local std::vector<int> v; // 本地变量
}
- thread_local变量是C++ 11新引入的一种存储类型。它会影响变量的存储周期(Storage duration),C++中有4种存储周期
- automatic- static- dynamic- thread
- 有且只有thread_local关键字修饰的变量具有线程周期(thread duration),这些变量(或者说对象)在线程开始的时候被生成(allocated),在线程结束的时候被销毁(deallocated)。并且每 一个线程都拥有一个独立的变量实例(Each thread has its own instance of the object)。
thread_local可以和static与extern关键字联合使用,这将影响变量的链接属性(to adjust linkage)。
适用范围:
- 命名空间下的全局变量- 类的static成员变量- 本地变量
- 既然每个线程都拥有一份独立的thread_local变量,那么就有2个问题需要考虑:
- 各线程的thread_local变量是如何初始化的?
每个线程都会进行一次单独初始化1. 各线程的thread_local变量在初始化之后拥有怎样的生命周期,特别是被声明为thread_local的本地变量(local variables)?- 声明为thread_local的本地变量在线程中是持续存在的,不同于普通临时变量的生命周期,它具有static变量一样的初始化特征和生命周期。
1.11 C++ 运算符
运算符是一种告诉编译器执行特定的数学或逻辑操作的符号
- 算术运算符- 关系运算符- 逻辑运算符- 位运算符- 赋值运算符- 杂项运算符
1.12 C++ 循环
循环类型
您可以在 while、for 或 do…while 循环内使用一个或多个循环。
1.13 C++ 判断
嵌套语句
? : 运算符
A ? B : c;
A真则B,A假为C。
1.14 C++函数
- 在函数声明中,参数的名称并不重要,只有参数的类型是必需的,因此第二条也是有效的声明:
int max(int num1, int num2); int max(int, int);
- 在一个源文件中定义函数且在另一个文件中调用函数时,函数声明是必需的。应该在调用函数的文件顶部声明函数。- 函数参数:
-
- - 函数使用参数,必须声明接受参数值的变量。这些变量称为函数的**形式参数**。 - 形式参数就像函数内的其他局部变量,在**进入函数时被创建,退出函数时被销毁**。
- 当调用函数时,有三种向函数传递参数的方式:
调用类型 描述 默认方法,该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数对实际参数没有影响。 该方法把参数的地址复制给形式参数。在函数内,该地址用于访问调用中要用到的实际参数。这意味着,**修改形式参数会影响实际参数**。 该方法把参数的引用复制给形式参数。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,**修改形式参数会影响实际参数**。 - 参数默认值 - 在函数定义中使用赋值运算符来为参数赋默认值。- 调用函数时,如果未传递参数的值,则会使用默认值,如果指定了值,则会忽略默认值,使用传递的值。 Lambda 函数与表达式 - C++11 提供了对匿名函数的支持,称为 Lambda 函数(也叫 Lambda 表达式)。 - Lambda 表达式把函数看作对象。Lambda 表达式可以像对象一样使用,比如可以将它们赋给变量和作为参数传递,还可以像函数一样对其求值。
- 形式:
[capture](parameters)->return-type{body} # eg: [](int x, int y){ return x < y ; }
无返回值:
capture{body}
eg:
[]{ ++global_x; }
返回值类型可以明确指定:
[](int x, int y) -> int { int z = x + y; return z + x; }
- 在Lambda表达式内可以访问 当前作用域 的变量,这是Lambda表达式的闭包(Closure)行为。 与JavaScript闭包不同,C++变量传递有传值和传引用的区别。可以通过前面的[]来指定:
[] // 沒有定义任何变量。使用未定义变量会引发错误。
[x, &y] // x以传值方式传入(默认),y以引用方式传入。
[&] // 任何被使用到的外部变量都隐式地以引用方式加以引用。
[=] // 任何被使用到的外部变量都隐式地以传值方式加以引用。
[&, x] // x显式地以传值方式加以引用。其余变量以引用方式加以引用。
[=, &z] // z显式地以引用方式加以引用。其余变量以传值方式加以引用。
- 对于[=]或[&]的形式,lambda 表达式可以直接使用 this 指针。但是,对于[]的形式,如果要使用 this 指针,必须显式传入:
this { this->someFunc(); }();
指针和引用的区别?- 指针是*,通过它能找到以它为地址的内存单元。- 引用是&,就是某个变量的别名,对引用操作与对变量直接操作完全一样。
- 区别:
- 引用必须要初始化。- 指针是个实体,而引用是别名。- 引用只能在定义时被初始化一次,之后不可变,指针可以改变所指的对象。- 可以有const指针,没有const引用。- 指针自加,是指向下一次地址,而引用自加:是本身值的增加。- 引用只能被初始化一次,不能再换成别的名字了。
指针调用与引用调用的区别在于:
- 指针变量存储的是一个指针,也就是一个存储地址,当使用指针作为形参的时候,可以改变指针的指向,访问该地址的数据时需要加上符号 * ;- 而引用调用他是直接将一个地址传递到函数内,相当于在该函数内给变量起了一个别名,不可改变指向,但可以改变该地址内所存储的值;
1.15 C++ 数字
- int rand(void) 与 void srand(unsigned int seed)
- - 头文件:`#include<stdlib.h>`- 用户未设定随机数种子时,系统默认的随机数种子为1。rand()产生的是伪随机数字,每次执行时是相同的;若要不同,用函数srand()初始化它。- rand()和srand()要一起使用,其中srand()用来初始化随机数种子,rand()用来产生随机数。- 参数seed必须是个整数,通常可以利用time(0)的返回值或NULL来当做seed。如果每次seed都设相同值,rand()所产生的随机数值每次就会一样。(失去随机性)- 可以把 seed 设为 `time(0)` 保证随机性。- 要产生 0–n 内的随机数:`rand() % n`;a–b 内的随机数 `a + rand() % (b - a + 1)`;左闭右开 ## 1.16 C++ 数组 - 存储一个**固定大小**的**相同**类型元素的顺序集合 - 所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素 ### 声明数组
- **arraySize** 必须是一个大于零的整数常量,**type** 可以是任意有效的 C++ 数据类型
type arrayName [ arraySize ]; - ### 初始化数组
- 如果不指定数组大小,则为初始化时元素的个数。
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};
多维数组
type name[size1][size2]...[sizeN];- eg:初始化二维数组
int a[3][4] = {
{0, 1, 2, 3} , /* 初始化索引号为 0 的行 /
{4, 5, 6, 7} , / 初始化索引号为 1 的行 /
{8, 9, 10, 11} / 初始化索引号为 2 的行 */
};
// 等同于:
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
指向数组的指针
- 数组名是一个指向数组中第一个元素的常量指针
- balance 是一个指向 &balance[0] 的指针,即数组 balance 的第一个元素的地址。
double balance[50];
// 把 p 赋值为 balance 的第一个元素的地址
double *p;
double balance[10];p = balance;
-*(balance + 4)等同于*(p+4)即访问balance[4]的数据从函数返回数组
- C++ 不允许返回一个完整的数组作为函数的参数;可以通过指定不带索引的数组名来返回一个指向数组的指针。
- 想要从函数返回一个一维数组,您必须声明一个返回指针的函数,如下:
int * myFunction()
{
.
.
.
}
- - C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量
- eg:生成 10 个随机数,并使用数组来返回它们
#include <iostream> #include <cstdlib> #include <ctime> using namespace std; // 要生成和返回随机数的函数 int * getRandom( ) { static int r[10]; // 设置种子 srand( (unsigned)time( NULL ) ); for (int i = 0; i < 10; ++i) { r[i] = rand(); cout << r[i] << endl; } return r; } // 要调用上面定义函数的主函数 int main () { // 一个指向整数的指针 int *p; p = getRandom(); for ( int i = 0; i < 10; i++ ) { cout << "*(p + " << i << ") : "; cout << *(p + i) << endl; } return 0; }1.17 C++ 字符串
- C++ 提供了以下两种类型的字符串表示形式:
- - C 风格字符串- C++ 引入的 string 类类型 ### C 风格字符串 - 字符串实际上是使用 **null** 字符 ‘\0’ 终止的一维字符数组。 - 字符数组的大小比实际的字符数多一个。 - C++ 编译器会在初始化数组时,自动把 ‘\0’ 放在字符串的末尾。
- 初始化:
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
// 或者:
char greeting[] = “Hello”;
序号函数 & 目的 |------ 1**strcpy(s1, s2);** 复制字符串 s2 到字符串 s1。 2**strcat(s1, s2);** 连接字符串 s2 到字符串 s1 的末尾。 3**strlen(s1);** 返回字符串 s1 的长度。(不包括 `"\0"` ) 4**strcmp(s1, s2);** 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回值小于 0;如果 s1>s2 则返回值大于 0。 5**strchr(s1, ch);** 返回一个指针,指向字符串 s1 中 **字符** ch 的第一次出现的位置。 6**strstr(s1, s2);** 返回一个指针,指向字符串 s1 中 **字符串** s2 的第一次出现的位置。
- 可用函数:- 示例:
#include <iostream> #include <cstring> using namespace std; int main () { char str1[11] = "Hello"; char str2[11] = "World"; char str3[11]; int len ; // 复制 str1 到 str3 strcpy( str3, str1); cout << "strcpy( str3, str1) : " << str3 << endl; // 连接 str1 和 str2 strcat( str1, str2); cout << "strcat( str1, str2): " << str1 << endl; // 连接后,str1 的总长度 len = strlen(str1); cout << "strlen(str1) : " << len << endl; system("pause"); return 0; } // strcpy( str3, str1) : Hello // strcat( str1, str2): HelloWorld // strlen(str1) : 10C++ 中的 String 类
- C++ 标准库提供了 string 类类型,支持上述所有的操作,另外还增加了其他更多的功能。
- 示例:
#include <iostream> #include <string> using namespace std; int main () { string str1 = "Hello"; string str2 = "World"; string str3; int len ; // 复制 str1 到 str3 str3 = str1; cout << "str3 : " << str3 << endl; // 连接 str1 和 str2 str3 = str1 + str2; cout << "str1 + str2 : " << str3 << endl; // 连接后,str3 的总长度 len = str3.size(); cout << "str3.size() : " << len << endl; system("pause"); return 0; } // str3 : Hello // str1 + str2 : HelloWorld // str3.size() : 101.18 C++ 指针
- 每个变量都有一个内存地址,用 “&” 访问地址。 - 指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。声明:
type *name;- 所有指针的值的实际数据类型(整型、浮点型、字符型等)都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。 - 示例:
#include <iostream> using namespace std; int main () { int var = 20; // 实际变量的声明 int *ip; // 指针变量的声明 ip = &var; // 在指针变量中存储 var 的地址 cout << "Value of var variable: "; cout << var << endl; // 输出在指针变量中存储的地址 cout << "Address stored in ip variable: "; cout << ip << endl; // 访问指针中地址的值 cout << "Value of *ip variable: "; cout << *ip << endl; return 0; } // Value of var variable: 20 // Address stored in ip variable: 0xbfc601ac // Value of *ip variable: 20- C++ 指针详解:
### 空指针 - 变量声明时,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为**空**指针。概念 描述 C++ 支持空指针。NULL 指针是一个定义在标准库中的值为零的常量。 可以对指针进行四种算术运算:++、–、+、- 指针和数组之间有着密切的关系。 可以定义用来存储指针的数组。 C++ 允许指向指针的指针。 通过引用或地址传递参数,使传递的参数在调用函数中被改变。 C++ 允许函数返回指针到局部变量、静态变量和动态内存分配。 - NULL 指针是一个定义在标准库中的值为零的常量:
- 内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。 - 因此,如果所有未使用的指针都被赋予空值,同时避免使用空指针,就可以防止误用一个未初始化的指针。很多时候,未初始化的变量存有一些垃圾值,导致程序难以调试。 - 空指针尽可能的使用 `nullptr`#include <iostream> using namespace std; int main () { int *ptr = NULL; cout << "ptr 的值是 " << ptr ; return 0; } // ptr 的值是 0指针的算数运算
- 指针是一个用数值表示的地址。因此,可以对指针执行算术运算。 - 可以对指针进行四种算术运算:++(指向下一个变量的地址)、–(指向上一个变量的地址)、+、-。
- 经常用指针代替数组,因为变量指针可以递增,而数组不能递增,因为数组是一个常量指针。示例:
- 指针的比较: 指针可以用关系运算符进行比较,如 ==、< 和 >。如果 p1 和 p2 指向两个相关的变量,比如同一个数组中的不同元素,则可对 p1 和 p2 进行大小比较。#include <iostream> using namespace std; const int MAX = 3; int main () { int var[MAX] = {10, 100, 200}; int *ptr; // 指针指向数组第一个元素地址 ptr = var; // 指针指向数组最后一个地址 // ptr = &var[MAX-1] for (int i = 0; i < MAX; i++) { cout << "Address of var[" << i << "] = "; cout << ptr << endl; cout << "Value of var[" << i << "] = "; cout << *ptr << endl; // 移动到下一个位置 ptr++; // 移动到上一个位置 ptr--; } return 0; }指针数组
- 让数组存储指向 int 或 char 或其他数据类型的指针 - 指向整数的指针数组的声明:
int *ptr[MAX];- 这里,把 ptr 声明为一个数组,由 MAX 个整数指针组成。因此,ptr 中的每个元素,都是一个指向 int 值的指针。 - 示例:把三个整数存储在一个指针数组中
#include <iostream> using namespace std; const int MAX = 3; int main () { int var[MAX] = {10, 100, 200}; int *ptr[MAX]; for (int i = 0; i < MAX; i++) { ptr[i] = &var[i]; // 赋值为整数的地址 } for (int i = 0; i < MAX; i++) { cout << "Value of var[" << i << "] = "; cout << *ptr[i] << endl; } return 0; } // Value of var[0] = 10 // Value of var[1] = 100 // Value of var[2] = 200- 指针数组 与 数组指针
- - int *p[n]为指针数组:即存放指针的数组,数组中有n个元素,每个元素为一个int型的指针;- int (*p)[n]为数组指针:即指向数组的指针,p为一个指针,指向一个包含n个int元素的数组。- 数组和指针,在后面的为主语,前面的是定语,用来修饰主语。 ### 指向指针的指针(多级间接寻址) - 指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。 - 通常,一个指针包含一个变量的地址。当定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。
- 指向指针的指针变量声明: -
int **var; - 示例:
#include <iostream> using namespace std; int main () { int var; int *ptr; int **pptr; var = 3000; // 获取 var 的地址 ptr = &var; // 使用运算符 & 获取 ptr 的地址 pptr = &ptr; // 使用 pptr 获取值 cout << "var 值为 :" << var << endl; cout << "*ptr 值为:" << *ptr << endl; cout << "**pptr 值为:" << **pptr << endl; return 0; } // var 值为 :3000 // *ptr 值为:3000 // **pptr 值为:3000传递指针给函数
- 方法:声明函数参数为指针类型
- 示例:传递一个无符号的 long 型指针给函数
#include <iostream> #include <ctime> using namespace std; void getSeconds(unsigned long *par); int main () { unsigned long sec; getSeconds( &sec ); // 输出实际值 cout << "Number of seconds :" << sec << endl; return 0; } void getSeconds(unsigned long *par) { // 获取当前的秒数 *par = time( NULL ); return; }传递指针给函数
- 只需要声明函数参数为指针类型,并将地址传递给函数
- 示例:传递一个无符号的 long 型指针给函数,并在函数内改变这个值
#include <iostream> #include <ctime> using namespace std; void getSeconds(unsigned long *par); int main () { unsigned long sec; getSeconds( &sec ); // 输出实际值 cout << "Number of seconds :" << sec << endl; return 0; } void getSeconds(unsigned long *par) { // 获取当前的秒数 *par = time( NULL ); return; }- 同样,可以将数组传递给函数(能接受指针作为参数的函数,也能接受数组作为参数):
#include <iostream> using namespace std; // 函数声明 double getAverage(int *arr, int size); int main () { // 带有 5 个元素的整型数组 int balance[5] = {1000, 2, 3, 17, 50}; double avg; // 传递一个指向数组的指针作为参数 avg = getAverage( balance, 5 ) ; // 输出返回值 cout << "Average value is: " << avg << endl; return 0; } double getAverage(int *arr, int size) { int i, sum = 0; double avg; for (i = 0; i < size; ++i) { sum += arr[i]; } avg = double(sum) / size; return avg; } // Average value is: 214.4从函数返回指针
- 首先声明一个返回指针的函数:(C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 **static** 变量。)
int * myFunction() { . . . } - 示例:
#include <iostream> #include <ctime> #include <cstdlib> using namespace std; // 要生成和返回随机数的函数 int * getRandom( ) { static int r[10]; // 设置种子 srand( (unsigned)time( NULL ) ); for (int i = 0; i < 10; ++i) { r[i] = rand(); cout << r[i] << endl; } return r; } // 要调用上面定义函数的主函数 int main () { // 一个指向整数的指针 int *p; p = getRandom(); for ( int i = 0; i < 10; i++ ) { cout << "*(p + " << i << ") : "; cout << *(p + i) << endl; } return 0; }1.19 C++ 引用
- 引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。- 一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
引用 vs 指针
区别:
- 不存在空引用。引用必须连接到一块合法的内存。- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。- 引用必须在创建时被初始化。指针可以在任何时间被初始化。- 引用不占内存,但指针是一个变量,是有自己的独立内存空间的
创建引用
- 变量名称是变量附属在内存位置中的标签,引用相当于是变量附属在内存位置中的第二个标签。可以通过原始变量名称或引用来访问变量的内容。
- 示例:
- 引用通常用于函数参数列表和函数返回值。#include <iostream> using namespace std; int main () { // 声明简单的变量 int i; double d; // 声明引用变量 int& r = i; double& s = d; i = 5; cout << "Value of i : " << i << endl; cout << "Value of i reference : " << r << endl; d = 11.7; cout << "Value of d : " << d << endl; cout << "Value of d reference : " << s << endl; return 0; } // Value of i : 5 // Value of i reference : 5 // Value of d : 11.7 // Value of d reference : 11.7引用作为参数
#include <iostream> using namespace std; // 函数声明 void swap(int& x, int& y); int main () { // 局部变量声明 int a = 100; int b = 200; cout << "交换前,a 的值:" << a << endl; cout << "交换前,b 的值:" << b << endl; /* 调用函数来交换值 */ swap(a, b); cout << "交换后,a 的值:" << a << endl; cout << "交换后,b 的值:" << b << endl; return 0; } // 函数定义 void swap(int& x, int& y) { int temp; temp = x; /* 保存地址 x 的值 */ x = y; /* 把 y 赋值给 x */ y = temp; /* 把 x 赋值给 y */ return; } // 交换前,a 的值: 100 // 交换前,b 的值: 200 // 交换后,a 的值: 200 // 交换后,b 的值: 100引用作为返回值
- 通过使用引用来替代指针,会使 C++ 程序更容易阅读和维护。函数返回一个引用的方式与返回一个指针类似。 - 当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边。
- 示例:
#include <iostream> using namespace std; double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0}; double& setValues( int i ) { return vals[i]; // 返回第 i 个元素的引用 } // 要调用上面定义函数的主函数 int main () { cout << "改变前的值" << endl; for ( int i = 0; i < 5; i++ ) { cout << "vals[" << i << "] = "; cout << vals[i] << endl; } setValues(1) = 20.23; // 改变第 2 个元素 setValues(3) = 70.8; // 改变第 4 个元素 cout << "改变后的值" << endl; for ( int i = 0; i < 5; i++ ) { cout << "vals[" << i << "] = "; cout << vals[i] << endl; } return 0; }- 当返回一个引用时,被引用的对象不能超出作用域。返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用。
int& func() { int q; // return q; // 在编译时发生错误 static int x; return x; // 安全,x 在函数作用域外依然是有效的 }1.20 C++ 日期 & 时间
- C++ 继承了 C 语言用于日期和时间操作的结构和函数。为了使用日期和时间相关的函数和结构,需要在 C++ 程序中引用
<ctime>头文件。 - 有四个与时间相关的类型:clock_t、time_t、size_t 和 tm。类型 clock_t、size_t 和 time_t 能够把系统时间和日期表示为某种整数。 结构类型 tm 把日期和时间以 C 结构的形式保存,tm 结构的定义如下:
struct tm {
int tm_sec; // 秒,正常范围从 0 到 59,但允许至 61
int tm_min; // 分,范围从 0 到 59
int tm_hour; // 小时,范围从 0 到 23
int tm_mday; // 一月中的第几天,范围从 1 到 31
int tm_mon; // 月,范围从 0 到 11
int tm_year; // 自 1900 年起的年数
int tm_wday; // 一周中的第几天,范围从 0 到 6,从星期日算起
int tm_yday; // 一年中的第几天,范围从 0 到 365,从 1 月 1 日算起
int tm_isdst; // 夏令时
}
- 函数:
- 参考: ## 1.21 C++ 基本输入输出序号 函数 & 描述 1 该函数返回系统的当前日历时间,自 1970 年 1 月 1 日以来经过的秒数。如果系统没有时间,则返回 .1。 2 该返回一个表示当地时间的字符串指针,字符串形式 **day month year hours:minutes:seconds year\n\0**。 3 该函数返回一个指向表示本地时间的 **tm** 结构的指针。 4 该函数返回程序执行起(一般为程序的开头),处理器时钟所使用的时间。如果时间不可用,则返回 .1。 5 该函数返回一个指向字符串的指针,字符串包含了 time 所指向结构中存储的信息,返回形式为:day month date hours:minutes:seconds year\n\0。 6 该函数返回一个指向 time 的指针,time 为 tm 结构,用协调世界时(UTC)也被称为格林尼治标准时间(GMT)表示。 7 该函数返回日历时间,相当于 time 所指向结构中存储的时间。 8 该函数返回 time1 和 time2 之间相差的秒数。 9 该函数可用于格式化日期和时间为指定的格式。 - C++ 继承了 C 语言用于日期和时间操作的结构和函数。为了使用日期和时间相关的函数和结构,需要在 C++ 程序中引用
- C++ 的 I/O 发生在流中,流是字节序列。
- - 如果字节流是从设备(如键盘、磁盘驱动器、网络连接等)流向内存,这叫做**输入操作**。- 如果字节流是从内存流向设备(如显示屏、打印机、磁盘驱动器、网络连接等),这叫做**输出操作**。 ### I/O 库头文件
- 头文件函数和描述 |------ `<iostream>`该文件定义了 **cin、cout、cerr** 和 **clog** 对象,分别对应于标准输入流、标准输出流、非缓冲标准错误流和缓冲标准错误流。 `<iomanip>`该文件通过所谓的参数化的流操纵器(比如 **setw** 和 **setprecision**),来声明对执行标准化 I/O 有用的服务。 `<fstream>`该文件为用户控制的文件处理声明服务。我们将在文件和流的相关章节讨论它的细节。
标准输出流(cout)
- 预定义的对象 cout 是 iostream 类的一个实例。cout 对象"连接"到标准输出设备,通常是显示屏。cout 是与流插入运算符 << 结合使用的。-
cout << "Value of str is : " << 变量名 << endl;
标准输入流(cin)
- 预定义的对象 cin 是 iostream 类的一个实例。cin 对象附属到标准输入设备,通常是键盘。cin 是与流提取运算符 >> 结合使用的,如下所示:-
cin >> name- 流提取运算符 >> 在一个语句中可以多次使用,如果要求输入多个数据,可以使用如下语句:cin >> name >> age;
标准错误流(cerr)
- 预定义的对象 cerr 是 iostream 类的一个实例。cerr 对象附属到标准错误设备,通常也是显示屏,但是 cerr 对象是非缓冲的,且每个流插入到 cerr 都会立即输出。
- cerr 也是与流插入运算符 << 结合使用的,示例:
#include <iostream> using namespace std; int main( ) { char str[] = "Unable to read...."; cerr << "Error message : " << str << endl; } // Error message : Unable to read....标准日志流(clog)
- 预定义的对象 clog 是 iostream 类的一个实例。clog 对象附属到标准错误设备,通常也是显示屏,但是 clog 对象是缓冲的。这意味着每个流插入到 clog 都会先存储在缓冲区,直到缓冲填满或者缓冲区刷新时才会输出。
- clog 也是与流插入运算符 << 结合使用的,如下所示:
#include <iostream> using namespace std; int main( ) { char str[] = "Unable to read...."; clog << "Error message : " << str << endl; } // Error message : Unable to read....1.22 C++ 结构体
- 结构体允许存储不同类型的数据项
- 定义:
struct type_name {
member_type1 member_name1;
member_type2 member_name2;
member_type3 member_name3;
.
.
} object_names;
- 访问结构成员
- - 使用**成员访问运算符(.)** 结构体可以作为函数参数
指向结构体的指针:
struct Books *struct_pointer;使用指向该结构的指针访问结构的成员,使用
->运算符typedef 关键字
- 预定义的对象 cout 是 iostream 类的一个实例。cout 对象"连接"到标准输出设备,通常是显示屏。cout 是与流插入运算符 << 结合使用的。-
- 为创建的结构体起“别名”:直接使用 Books 来定义 Books 类型的变量,不需要使用 struct 关键字。
typedef struct Books { char title[50]; char author[50]; char subject[100]; int book_id; }Books; // 使用: Books Book1, Book2;- 也可以定义非结构体类型: x, y 和 z 都是指向长整型 long int 的指针。
typedef long int *pint32; pint32 x, y, z;
- typedef 与 #define 的区别:
-
- typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
- typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。
1229





