C++

C++入门(一)

Posted by alonealice on 2021-02-08

类和对象

C语言中的结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
//定义结构体 Student
struct Student{
//结构体包含的成员变量
char *name;
int age;
float score;
};
void display(struct Student stu){
printf("%s的年龄是 %d,成绩是 %f\n", stu.name, stu.age, stu.score);
}
int main(){
//为结构体的成员变量赋值
stu1.name = "小明";
stu1.age = 15;
stu1.score = 92.5;
display(stu1);
return 0;
}

C++中的类也是一种构造类型,但是进行了一些扩展,类的成员不但可以是变量,还可以是函数;通过类定义出来的变量也有特定的称呼,叫做“对象”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
//通过class关键字类定义类
class Student{
public:
//类包含的变量
char *name;
int age;
float score;
//类包含的函数
void say(){
printf("%s的年龄是 %d,成绩是 %f\n", name, age, score);
}
};
int main(){
//通过类来定义变量,即创建对象
class Student stu1; //也可以省略关键字class
//为类的成员变量赋值
stu1.name = "小明";
stu1.age = 15;
stu1.score = 92.5f;
//调用类的成员函数
stu1.say();
return 0;
}

C语言中的 struct 只能包含变量,而C++中的 class 除了可以包含变量,还可以包含函数。

命名空间

命名空间主要是用来解决合作开发时的命名冲突问题,有时也被称为名字空间、名称空间。

语法格式为:

1
2
3
namespace name{
//variables, functions, classes
}

举例:

1
2
3
4
5
6
7
namespace Li{  //小李的变量定义    
FILE fp = NULL;
}

namespace Han{ //小韩的变量定义
FILE fp = NULL
}

使用:

1
2
Li :: fp = fopen("one.txt", "r");  //使用小李定义的变量 fp
Han :: fp = fopen("two.txt", "rb+"); //使用小韩定义的变量 fp

是一个新符号,称为域解析操作符,在C++中用来指明要使用的命名空间。

除了直接使用域解析操作符,还可以采用 using 声明,例如:

1
2
3
using Li :: fp;
fp = fopen("one.txt", "r"); //使用小李定义的变量 fp
Han :: fp = fopen("two.txt", "rb+"); //使用小韩定义的变量 fp

在代码的开头用using声明了 Li::fp,它的意思是,using 声明以后的程序中如果出现了未指明命名空间的 fp,就使用 Li::fp;但是若要使用小韩定义的 fp,仍然需要 Han::fp。

using 声明不仅可以针对命名空间中的一个变量,也可以用于声明整个命名空间,例如:

1
2
3
using namespace Li;
fp = fopen("one.txt", "r"); //使用小李定义的变量
fpHan :: fp = fopen("two.txt", "rb+"); //使用小韩定义的变量 fp

如果命名空间 Li 中还定义了其他的变量,那么同样具有 fp 变量的效果。在 using 声明后,如果有未具体指定命名空间的变量产生了命名冲突,那么默认采用命名空间 Li 中的变量。

命名空间内部不仅可以声明或定义变量,对于其它能在命名空间以外声明或定义的名称,同样也都能在命名空间内部进行声明或定义,例如类、函数、typedef、#define 等都可以出现在命名空间中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
//将类定义在命名空间中
namespace Diy{
class Student{
public:
char *name;
int age;
float score;

public:
void say(){
printf("%s的年龄是 %d,成绩是 %f\n", name, age, score);
}
};
}
int main(){
Diy::Student stu1;
stu1.name = "小明";
stu1.age = 15;
stu1.score = 92.5f;
stu1.say();
return 0;
}

标准库和std命名空间

  • iostream.h:用于控制台输入输出头文件。
  • fstream.h:用于文件操作的头文件。
  • complex.h:用于复数计算的头文件。

和C语言一样,C++ 头文件仍然以.h为后缀,它们所包含的类、函数、宏等都是全局范围的。

后来 C++ 引入了命名空间的概念,计划重新编写库,将类、函数、宏等都统一纳入一个命名空间,这个命名空间的名字就是std。std 是 standard 的缩写,意思是“标准命名空间”。

为了避免头文件重名,新版 C++ 库也对头文件的命名做了调整,去掉了后缀.h,所以老式 C++ 的iostream.h变成了iostreamfstream.h变成了fstream。而对于原来C语言的头文件,也采用同样的方法,但在每个名字前还要添加一个c字母,所以C语言的stdio.h变成了cstdiostdlib.h变成了cstdlib

可以发现,对于不带.h的头文件,所有的符号都位于命名空间 std 中,使用时需要声明命名空间 std;对于带.h的头文件,没有使用任何命名空间,所有符号都位于全局作用域。这也是 C++ 标准所规定的。

不过现实情况和 C++ 标准所期望的有些不同,对于原来C语言的头文件,即使按照 C++ 的方式来使用,即#include <cstdio>这种形式,那么符号可以位于命名空间 std 中,也可以位于全局范围中,请看下面的两段代码。

  1. 使用命名空间 std:
1
2
3
4
5
#include <cstdio>
int main(){
std::printf("http://c.biancheng.net\n");
return 0;
}
  1. 不使用命名空间 std:
1
2
3
4
5
#include <cstdio>
int main(){
printf("http://c.biancheng.net\n");
return 0;
}

大部分编译器在实现时并没有严格遵循标准,它们对两种写法都支持,程序员可以使用 std 也可以不使用。第 1) 种写法是标准的,第 2) 种不标准.

在 main() 函数中声明命名空间 std,它的作用范围就位于 main() 函数内部,如果在其他函数中又用到了 std,就需要重新声明,请看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
void func(){
//必须重新声明
using namespace std;
cout<<"http://c.biancheng.net"<<endl;
}
int main(){
//声明命名空间std
using namespace std;

cout<<"C语言中文网"<<endl;
func();
return 0;
}

如果希望在所有函数中都使用命名空间 std,可以将它声明在全局范围中。

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
//声明命名空间std
using namespace std;
void func(){
cout<<"http://c.biancheng.net"<<endl;
}
int main(){
cout<<"C语言中文网"<<endl;
func();
return 0;
}

将 std 直接声明在所有函数外部,这样虽然使用方便,但在中大型项目开发中是不被推荐的,这样做增加了命名冲突的风险,推荐在函数内部声明 std。

C++输入输出

在C++语言中,C语言的这一套输入输出库我们仍然能使用,但是 C++ 又增加了一套新的、更容易使用的输入输出库。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<iostream>
using namespace std;
int main(){
int x;
float y;
cout<<"Please input an int number:"<<endl; //输出一句话
cin>>x; //等待输入,并赋值给x
cout<<"The int number is x= "<<x<<endl; //输出
cout<<"Please input a float number:"<<endl;
cin>>y;
cout<<"The float number is y= "<<y<<endl;
return 0;
}

使用 cout 进行输出时需要紧跟<<运算符,使用 cin 进行输入时需要紧跟>>运算符,这两个运算符可以自行分析所处理的数据类型,因此无需像使用 scanf 和 printf 那样给出格式控制字符串。其中endl表示换行。

cin 和 cout都支持连续输入和输出

1
cin>>x>>y;

C++ 与 C 的改进

C 规定,所有局部变量都必须定义在函数开头,C99 标准取消这这条限制,但是 VC/VS 对 C99 的支持很不积极,仍然要求变量定义在函数开头。但是C++ 取消了原来的限制,变量只要在使用之前定义好即可,不强制必须在函数开头定义所有变量,VC/VS 下就不会报错。

C++ 新增了bool类型,它一般占用 1 个字节长度。bool 类型只有两个取值,true 和 false。但是输出 bool 变量的值时还是用数字 1 和 0 表示。

const关键字

定义const对象

const修饰符可以把对象转变成常数对象,意思就是说利用const进行修饰的变量的值在程序的任意位置将不能再被修改。因为常量在定以后就不能被修改,所以定义时必须初始化。

const对象默认为文件的局部变量

在全局作用域里定义非const变量时,它在整个程序中都可以访问。在全局作用域声明的const变量是定义该对象的文件的局部变量。此变量只存在于那个文件中,不能被其他文件访问。通过指定const变量为extern,就可以在整个程序中访问const对象。非const变量默认为extern。

const 引用

1
2
3
4
const int a=1;
const int &b=a;
int &c = a; //错误
const int &d=3;

const对象不能绑定到非const 的引用上,反之,const引用可以绑定非const对象。

const对象的动态数组

在自由存储区中创建的数组存储了内置类型的const对象,则必须为这个数组提供初始化: 因为数组元素都是const对象,无法赋值。实现这个要求的唯一方法是对数组做值初始化。同时C++允许定义类类型的const数组,但该类类型必须提供默认构造函数:

1
2
3
4
5
6
//error
const int *a = new const int[100];
//ok
const int *b = new const int[100]();
//ok
const string *c = new string[100];//这里便会调用string类的默认构造函数初始化数组元素。

指针和const

指向常量的指针: C++为了保证不允许使用指针改变所指的const值这个特性,强制要求这个指针也必须具备const特性。

1
2
3
4
5
const int a = 21;
//error
int *b = &a;
//ok
const int *b = &a;

允许把非const对象的地址赋给指向const对象的指针

1
2
3
4
5
int a = 21;
//ok
int *b = &a;
//ok
const int *b = &a;

常指针(const指针)

1
2
int a = 21;
int *const b = &a;

可以从右往左把上述定义语句读作"指向int型对象的const指针"。与其他const量一样,const指针的值不能被修改,这意味着不能使curErr指向其他对象。Const指针也必须在定义的时候初始化。

指向常量的常指针(指向const对象的const指针)

1
2
const int a = 21;
const int *const b = &a;

函数和const

在一个类中,任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其它非const成员函数,编译器将指出错误,这无疑会提高程序的健壮性(const成员函数只能调用其他const成员函数)。使用const关键字进行说明的成员函数,称为常成员函数。只有常成员函数才有资格操作常量或常对象,没有使用const关键字说明的成员函数不能用来操作常对象。

1
2
3
4
5
6
7
8
class A{
public:
int a(void)const;
};

int A::a(void)const{

}

const是定义为const函数的组成部分,那么就可以通过添加const实现函数重载。

const 修饰函数返回值:它的含义和const修饰普通变量以及指针的含义基本相同

const修饰函数参数:传递过来的参数在函数内不可以改变,如果是指针,则参数指针所指内容为常量不可变,如果参数为引用,为了增加效率同时防止修改

const限定符和static的区别

static静态成员变量不能在类的内部初始化。在类的内部只是声明,定义必须在类定义体的外部,通常在类的实现文件中初始化。const成员变量也不能在类定义处初始化,只能通过构造函数初始化列表进行,并且必须有构造函数

new和delete操作符

在C语言中,动态分配内存用 malloc() 函数,释放内存用 free() 函数。如下所示:

1
int *p = (int*) malloc( sizeof(int) * 10 );  //分配10个int型的内存空间free(p);  //释放内存

在C++中,这两个函数仍然可以使用,但是C++又新增了两个关键字,new 和 delete:new 用来动态分配内存,delete 用来释放内存。

用 new 和 delete 分配内存更加简单:

1
int *p = new int;  //分配1个int型的内存空间delete p;  //释放内存

如果希望分配一组连续的数据,可以使用 new[]:

1
int *p = new int[10];  //分配10个int型的内存空间delete[] p;

内联函数 inline

在编译时将函数调用处用函数体替换,类似于C语言中的宏展开。这种在函数调用处直接嵌入函数体的函数称为内联函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//内联函数,交换两个数的值
inline void swap(int *a, int *b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}
int main(){
int m, n;
cin>>m>>n;
cout<<m<<", "<<n<<endl;
swap(&m, &n);
cout<<m<<", "<<n<<endl;
return 0;
}

要在函数定义处添加 inline 关键字,在函数声明处添加 inline 关键字虽然没有错,但这种做法是无效的,编译器会忽略函数声明处的 inline 关键字。内联函数不应该有声明,应该将函数定义放在本应该出现函数声明的地方

使用内联函数的缺点也是非常明显的,编译后的程序会存在多份相同的函数拷贝,如果被声明为内联函数的函数体非常大,那么编译后的程序体积也将会变得很大,所以再次强调,一般只将那些短小的、频繁调用的函数声明为内联函数。

C语言宏:是预处理命令的一种,它允许用一个标识符来表示一个字符串(常量或者是表达式)

函数的默认参数

定义函数时可以给形参指定一个默认的值,这样调用函数时如果没有给这个形参赋值。同时调用函数时可以省略有默认值的参数。

1
2
3
void func(int n, float b=1.2, char c='@'){
cout<<n<<", "<<b<<", "<<c<<endl;
}

默认参数除了使用数值常量指定,也可以使用表达式指定。默认参数只能放在形参列表的最后,而且一旦为某个形参指定了默认值,那么它后面的所有形参都必须有默认值。

函数重载

C++代码在编译时会根据参数列表对函数进行重命名,例如void Swap(int a, int b)会被重命名为_Swap_int_intvoid Swap(float x, float y)会被重命名为_Swap_float_float。当发生函数调用时,编译器会根据传入的实参去逐个匹配,以选择对应的函数,如果匹配失败,编译器就会报错,这叫做重载决议。