文章目录
1 字符串
有两种类型的字符串表示形式:
- C 风格字符串
- C++ 引入的 string 类类型
1.1 C 风格字符串
字符串实际上是使用 null 字符 \0 终止的一维字符数组。下面的声明和初始化创建了一个 RUNOOB 字符串。由于在数组的末尾存储了空字符,所以字符数组的大小比单词 RUNOOB 的字符数多一个。
char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};
也可以把上面的语句写成:
char site[]="RUNOOB";
输出字符串的示例代码如下:
#include<iostream>
using namespace std;
int main(){
char site[7]={'R', 'U', 'N', 'O', 'O', 'B', '\0'};
cout<<"火影忍者:"<<site<<endl;
return 0;
}
输出如下:
火影忍者:RUNOOB
下面介绍了一些用于字符串操作的函数:
1、strcpy(s1, s2);
复制字符串 s2 到字符串 s1。
2、strcat(s1, s2);
连接字符串 s2 到字符串 s1 的末尾。连接字符串也可以用 + 号,例如:
string str1 = “runoob”;
string str2 = “google”;
string str = str1 + str2;
3、strlen(s1);
返回字符串 s1 的长度。
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[13]="naruto";
char str2[13]="hinata";
char str3[13];
int len;
strcpy(str3,str1); //将str1复制到str3
cout<<"1、"<<str3<<endl;
strcat(str1,str2); //连接str1和str2
cout<<"2、"<<str1<<endl;
len=strlen(str1);
cout<<"3、"<<len<<endl;
return 0;
}
输出如下:
1、naruto
2、narutohinata
3、12
1.2 C++ 引入的 string 类类型
C++ 标准库提供了 string 类类型,支持上述所有的操作,另外还增加了其他更多的功能。示例代码如下:
#include<iostream>
#include<string>
using namespace std;
int main(){
string str1="naruto";
string str2="hinata";
string str3;
int len;
str3=str1; //将str1复制到str3
cout<<"1、"<<str3<<endl;
str3=str1+str2; //连接str1和str2
cout<<"2、"<<str3<<endl;
len=str3.size();
cout<<"3、"<<len<<endl;
return 0;
}
输出如下:
1、naruto
2、narutohinata
3、12
2 指针
下面的代码将输出变量的地址:
#include <iostream>
using namespace std;
int main()
{
int var1;
char var2[10];
cout<<"var1的地址:"<<&var1<<endl;
cout<<"var2的地址:"<<&var2<<endl;
return 0;
}
输出如下:
var1的地址:0x7fffe9e8bb8c
var2的地址:0x7fffe9e8bb82
在了解内存地址以及如何访问它后,下面介绍指针。
指针也是一个变量,其值为另一个变量的地址。指针变量声明的一般形式为:
type *var-name;
type 是指针的类型,var-name 是指针变量的名称,星号是用来指定一个变量是指针。以下是有效的指针声明示例:
int *a;
float *b;
char *c;
所有指针的值的实际数据类型都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
使用指针时会频繁进行以下几个操作:
- 定义一个指针变量
- 把变量地址赋值给指针
- 访问指针变量中可用地址的值
上述操作是通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值。下面的实例涉及到了这些操作:
#include <iostream>
using namespace std;
int main()
{
int var=20;
int *ip;
ip=&var; //在指针变量中存储var的地址
cout<<"变量的值为:"<<var<<endl;
cout<<"指针变量中存储的地址是:"<<ip<<endl;
cout<<"指针指向地址的值为:"<<*ip<<endl;
return 0;
}
输出如下:
变量的值为:20
指针变量中存储的地址是:0x7ffd91c127f4
指针指向地址的值为:20
下面是一些与指针相关的重要概念:
2.1 Null 指针
赋为 NULL 值的指针被称为空指针。NULL 指针是一个定义在标准库中的值为零的常量。请看下面的程序:
#include <iostream>
using namespace std;
int main()
{
int *ptr=NULL;
cout<<"指针ptr的值为:"<<ptr<<endl;
return 0;
}
输出如下:
指针ptr的值为:0
内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。如果指针包含空值(零值),则假定它不指向任何东西。如需检查一个空指针,可以使用 if 语句如下所示:
if(ptr) //如果ptr非空,则...
if(!ptr) //如果ptr为空,则...
2.2 指针的算术运算
指针是一个用数值表示的地址,可以对指针执行四种算术运算:++、–、+、-。
假设 ptr 是一个指向地址 1000 的整型指针,是一个 32 位的整数,若对该指针执行下列的算术运算:
ptr++
在执行完上述的运算之后,ptr 将指向位置 1004,即当前位置往后移 4 个字节(因为一个int变量占用4个字节)。这个运算会在不影响内存位置中实际值的情况下,移动指针到下一个内存位置。如果 ptr 指向一个地址为 1000 的字符,上面的运算会导致指针指向位置 1001,因为下一个字符位置是在 1001。
一般在程序中使用指针代替数组,因为变量指针可以递增,而数组不能递增,因为数组是一个常量指针。下面的程序递增变量指针,以便顺序访问数组中的每一个元素:
#include <iostream>
using namespace std;
const int MAX=3;
int main()
{
int *ptr;
int var[MAX]={10,100,200};
ptr=var;
for(int i=0;i<MAX;i++){
cout<<"变量["<<i<<"]的地址为:"<<ptr<<endl;
cout<<"变量["<<i<<"]的值为:"<<*ptr<<endl;
ptr++;
}
return 0;
}
输出如下:
变量[0]的地址为:0x7ffe6afc2948
变量[0]的值为:10
变量[1]的地址为:0x7ffe6afc294c
变量[1]的值为:100
变量[2]的地址为:0x7ffe6afc2950
变量[2]的值为:200
下面的代码对指针进行递减运算,即把值减去其数据类型的字节数:
#include <iostream>
using namespace std;
const int MAX=3;
int main()
{
int *ptr;
int var[MAX]={10,100,200};
ptr=&var[MAX-1];
for(int i=MAX;i>0;i--){
cout<<"变量["<<i<<"]的地址为:"<<ptr<<endl;
cout<<"变量["<<i<<"]的值为:"<<*ptr<<endl;
ptr--;
}
return 0;
}
输出如下:
变量[3]的地址为:0x7ffe6df14150
变量[3]的值为:200
变量[2]的地址为:0x7ffe6df1414c
变量[2]的值为:100
变量[1]的地址为:0x7ffe6df14148
变量[1]的值为:10
指针可以用关系运算符进行比较,如 ==、< 和 >。如果 p1 和 p2 指向两个相关的变量,比如同一个数组中的不同元素,则可对 p1 和 p2 进行大小比较。
2.3 指针 vs 数组
指针和数组在很多情况下是可以互换的。例如,一个指向数组开头的指针,可以通过使用指针的算术运算或数组索引来访问数组。请看下面的程序:
#include <iostream>
using namespace std;
const int MAX=3;
int main()
{
int *ptr;
int var[MAX]={10,100,200};
ptr=var;
for(int i=0;i<MAX;i++){
cout<<"变量["<<i<<"]的地址为:"<<ptr<<endl;
cout<<"变量["<<i<<"]的值为:"<<*ptr<<endl;
ptr++;
}
return 0;
}
输出如下:
变量[0]的地址为:0x7ffc01e6a488
变量[0]的值为:10
变量[1]的地址为:0x7ffc01e6a48c
变量[1]的值为:100
变量[2]的地址为:0x7ffc01e6a490
变量[2]的值为:200
但是,指针和数组并不可以完全互换。请看下面的程序:
#include <iostream>
using namespace std;
const int MAX=3;
int main()
{
int var[MAX]={10,100,200};
for(int i=0;i<MAX;i++){
*var=i; //这是正确的
//var++; //这是错误的
}
return 0;
}
上述代码中,第9行把指针运算符 * 应用到 var 上是完全可以的,但第10行修改 var 的值是非法的——这是因为 var 是一个指向数组开头的常量,不能作为左值。
由于一个数组名对应一个指针常量,只要不改变数组的值,仍然可以用指针形式的表达式。例如,下面是一个有效的语句,把 var[2] 赋值为 500:
*(var+2)=500;
2.4 指针数组
可以让数组存储【指向 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<<"变量"<<i<<"的值为"<<*ptr[i]<<endl;
}
return 0;
}
输出如下:
变量0的值为10
变量1的值为100
变量2的值为200
2.5 指向指针的指针
指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。指针的指针就是将指针的地址存放在另一个指针里面。
通常,一个指针包含一个变量的地址。当定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。
一个指向指针的指针变量在声明时需要在变量名前放置两个星号。例如,下面声明了一个指向 int 类型指针的指针:
int **var;
当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符,如:
#include <iostream>
using namespace std;
int main()
{
int var=300;
int *ptr;
int **pptr; //指针的指针
ptr=&var; //获取var的地址
pptr=&ptr; //获取指针ptr的地址
cout<<"var的值为:"<<var<<endl;
cout<<"*ptr的值为:"<<*ptr<<endl;
cout<<"**pptr的值为:"<<**pptr<<endl;
return 0;
}
输出如下:
var的值为:300
*ptr的值为:300
**pptr的值为:300
2.6 传递指针给函数
可以声明函数参数为指针类型。在下面的实例中,传递一个无符号的 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;
}
输出如下:
Number of seconds:1638190022
函数中可以将指针作为参数,也可以将数组作为参数,如下所示:
#include <iostream>
using namespace std;
double getAverage(int* arr, int size);
int main()
{
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
2.7 从函数返回指针
可以声明一个返回指针的函数,如下所示:
int *myFunction(){
...
}
C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static变量。
下面的代码会生成 10 个随机数,并使用表示指针的数组名(即第一个数组元素的地址)来返回它们,具体如下:
#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 << "):" << *(p + i) << endl;
}
return 0;
}
输出如下:
7405
15856
24449
13688
27204
13240
15362
24743
14302
10682
*(p+0):7405
*(p+1):15856
*(p+2):24449
*(p+3):13688
*(p+4):27204
*(p+5):13240
*(p+6):15362
*(p+7):24743
*(p+8):14302
*(p+9):10682
3 引用
引用变量是一个别名,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
引用与指针有三个主要的不同:
- 不存在空引用。引用必须连接到一块合法的内存。
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
- 引用必须在创建时被初始化。指针可以在任何时间被初始化。
可以通过原始变量名称或引用来访问变量的内容。例如对于:
int i=17;
可以为 i 声明引用变量,如下所示:
int& r=i; //r 是一个初始化为 i 的整型引用
double& s=d; //s 是一个初始化为 d 的 double 型引用
& 读作引用。下面的实例使用了 int 和 double 引用:
#include<iostream>
using namespace std;
int main(){
int i;
double d;
int& r=i;
double& s=d;
i=5;
d=11.1;
cout<<"i:"<<i<<endl;
cout<<"the reference of r:"<<r<<endl;
cout<<"d:"<<d<<endl;
cout<<"the reference of s:"<<s<<endl;
}
输出如下:
i:5
the reference of r:5
d:11.1
the reference of s:11.1
引用通常用于函数参数列表和函数返回值。下面是两个与 C++ 引用相关的重要概念:
1、把引用作为参数,C++ 支持把引用作为参数传给函数,这比传一般的参数更安全。下面的实例使用了引用来实现引用调用函数:
#include<iostream>
using namespace std;
void swap(int& x, int& y);
int main() {
int a = 100, 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 = y;
y = temp;
return;
}
输出如下:
交换前a:100
交换前b:200
交换后a:200
交换后b:100
2、把引用作为函数的返回值,当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边。示例代码如下:
#include<iostream>
using namespace std;
double vals[] = { 10.1,12.6,33.1,24.1,50.0 };
double& setValues(int i) {
double& ref = vals[i];
return ref; //返回第i个元素的引用
}
int main() {
cout << "改变前的值" << endl;
for (int i = 0; i < 5; i++) {
cout << "vals[" << i << "]=" << vals[i] << endl;
}
setValues(1) = 20.23; //改变第2个元素
setValues(3) = 70.9; //改变第2个元素
cout << "改变后的值" << endl;
for (int i = 0; i < 5; i++) {
cout << "vals[" << i << "]=" << vals[i] << endl;
}
return 0;
}
输出如下:
改变前的值
vals[0]=10.1
vals[1]=12.6
vals[2]=33.1
vals[3]=24.1
vals[4]=50
改变后的值
vals[0]=10.1
vals[1]=20.23
vals[2]=33.1
vals[3]=70.9
vals[4]=50
当返回一个引用时,要注意被引用的对象不能超出作用域。所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用。示例代码如下:
int& func() {
int q;
//return q; //此句是错误的,会在编译时发生错误
static int x;
return x; //正确的,可以返回一个对静态变量的引用
}
4 日期与时间
C++ 继承了 C 语言用于日期和时间操作的结构和函数。为了使用日期和时间相关的函数和结构,需要在 C++ 程序中引用 <ctime>
头文件。有四个与时间相关的类型:clock_t、time_t、size_t 和 tm,其中类型 clock_t、size_t 和 time_t 能够把系统时间和日期表示为某种整数,结构类型 tm 把日期和时间以 C 结构的形式保存。
这里省去了原教程中提到的几个关于日期与时间的函数,可以参考这里。
下面的实例获取当前系统的日期和时间:
#include<iostream>
#include<ctime>
using namespace std;
int main() {
time_t now = time(0); //基于当前系统的当前日期与时间
char* dt = ctime(&now); //将now转换为字符串形式
cout << "本地日期与时间:" << dt << endl;
return 0;
}
输出如下:
本地日期与时间:Thu Dec 2 06:56:41 2021
5 基本输入输出
C++ 的 I/O 发生在流中,流是字节序列。如果字节流是从设备(如键盘、磁盘驱动器、网络连接等)流向内存,这叫做输入操作。如果字节流是从内存流向设备(如显示屏、打印机、磁盘驱动器、网络连接等),这叫做输出操作。
头文件<iostream>
定义了 cin、cout、cerr 和 clog 对象,分别对应于标准输入流、标准输出流、非缓冲标准错误流和缓冲标准错误流。
5.1 标准输出流(cout)
预定义的对象 cout 是 iostream 类的一个实例。cout 对象"连接"到标准输出设备,通常是显示屏。cout 是与流插入运算符 << 结合使用的,如下所示:
#include<iostream>
using namespace std;
int main() {
cout << "Hello World" << endl;
return 0;
}
输出如下:
Hello World
流插入运算符 << 在一个语句中可以多次使用,endl 用于在行末添加一个换行符。
5.2 标准输入流(cin)
预定义的对象 cin 是 iostream 类的一个实例。cin 对象附属到标准输入设备,通常是键盘。cin 是与流提取运算符 >> 结合使用的,如下所示:
#include<iostream>
using namespace std;
int main() {
int age;
cout << "您的年龄是:";
cin >> age;
cout << "您已经" << age << "岁了" << endl;
return 0;
}
输出如下:
您的年龄是:24
您已经24岁了
流提取运算符 >> 在一个语句中可以多次使用,如果要求输入多个数据,可以使用如下语句:
cin>>name>>age;
这相当于:
cin>>name;
cin>>age;
5.3 标准错误流(cerr)
预定义的对象 cerr 是 iostream 类的一个实例。cerr 对象附属到标准错误设备,通常也是显示屏,但是 cerr 对象是非缓冲的,且每个流插入到 cerr 都会立即输出。cerr 也是与流插入运算符 << 结合使用的,如下所示:
#include<iostream>
using namespace std;
int main() {
char str[] = "Unable to read...";
cerr << "Error messgae:" << str << endl;
return 0;
}
输出如下:
Error messgae:Unable to read...
5.4 标准日志流(clog)
预定义的对象 clog 是 iostream 类的一个实例。clog 对象附属到标准错误设备,通常也是显示屏,但是 clog 对象是缓冲的。这意味着每个流插入到 clog 都会先存储在缓冲区,直到缓冲填满或者缓冲区刷新时才会输出。clog 也是与流插入运算符 << 结合使用的,如下所示:
#include<iostream>
using namespace std;
int main() {
char str[] = "Unable to read...";
clog << "Error messgae:" << str << endl;
return 0;
}
输出如下:
Error messgae:Unable to read...
通过这些小实例无法区分 cout、cerr 和 clog 的差异,但在编写和执行大型程序时,它们之间的差异就变得非常明显。一般来说,使用 cerr 流来显示错误消息,而其他的日志消息则使用 clog 流来输出。
6 结构体struct
使用 struct 语句来定义结构体,struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下:
struct type_name {
member_type1 member_name1;
member_type2 member_name2;
member_type3 member_name3;
...
...
}object_names;
type_name 是结构体体类型的名称,member_type1 member_name1 是标准的变量定义,比如 int i。在结构体定义的末尾,最后一个分号之前,可以指定一个或多个结构体变量,这是可选的。下面是声明一个结构体类型 Books,变量为 book:
struct Books {
char title[50];
char author[50];
char subject[100];
char book_id;
}book;
为了访问结构体的成员,使用成员访问运算符(.)。成员访问运算符是结构体变量名称和要访问的结构体成员之间的一个句号。下面的实例演示了结构体的用法:
#include<iostream>
#include<cstring>
using namespace std;
struct Books {
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main(){
struct Books Book;
strcpy_s(Book.title, "Python教程");
strcpy_s(Book.author, "许嵩");
strcpy_s(Book.subject, "计算机");
Book.book_id = 123456;
cout << "标题:" << Book.title << endl;
cout << "作者:" << Book.author << endl;
cout << "类目:" << Book.subject << endl;
cout << "ID:" << Book.book_id << endl;
return 0;
}
输出如下:
标题:Python教程
作者:许嵩
类目:计算机
ID:123456
可以把结构体作为函数参数,传参方式与其他类型的变量或指针类似。示例代码如下:
#include<iostream>
#include<cstring>
using namespace std;
void printBook(struct Books book);
struct Books {
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main() {
Books Book;
strcpy_s(Book.title,"Python教程");
strcpy_s(Book.author,"许嵩");
strcpy_s(Book.subject,"计算机");
Book.book_id = 123456;
printBook(Book);
return 0;
}
void printBook(struct Books book) {
cout << "标题:" << book.title << endl;
cout << "作者:" << book.author << endl;
cout << "类目:" << book.subject << endl;
cout << "ID:" << book.book_id << endl;
}
输出如下:
标题:Python教程
作者:许嵩
类目:计算机
ID:123456
可以定义指向结构体的指针,方式与定义指向其他类型变量的指针相似,如下所示:
struct Books *struct_pointer;
可以在上述定义的指针变量中存储结构体变量的地址。为了查找结构体变量的地址,请把 & 运算符放在结构体名称的前面,如下所示:
struct_pointer=&Book;
为了使用指向该结构体的指针访问结构体的成员,需要使用 -> 运算符,如下所示:
struct_pointer->title;
下面使用结构体指针来重写上面的实例:
#include<iostream>
#include<cstring>
using namespace std;
void printBook(struct Books *book);
struct Books {
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main() {
Books Book;
strcpy_s(Book.title,"Python教程");
strcpy_s(Book.author,"许嵩");
strcpy_s(Book.subject,"计算机");
Book.book_id = 123456;
printBook(&Book);
return 0;
}
void printBook(struct Books *book) {
cout << "标题:" << book->title << endl;
cout << "作者:" << book->author << endl;
cout << "类目:" << book->subject << endl;
cout << "ID:" << book->book_id << endl;
}
输出如下:
标题:Python教程
作者:许嵩
类目:计算机
ID:123456
使用typedef可以更简单的定义结构体的方式,这样可以为创建的类型取一个"别名"。例如:
typedef struct Books {
char title[50];
char author[50];
char subject[100];
int book_id;
}Books;
现在可以直接使用 Books 来定义 Books 类型的变量,而不需要使用 struct 关键字。如:
Books Book;
可以使用 typedef 关键字来定义非结构体类型,如下所示:
typedef long int* pint32;
pint32 x; //x是指向长整型 long int 的指针
END