从C到C++

本文最后更新于:2023年8月7日 凌晨

本笔记根据《C/C++语言程序设计》龚尚福版以及个人理解总结修改,不能保证内容完全准确,仅供参考。

第一次修改(2019年8月4日):C++对于C的一般扩充

第二次修改(2019年8月5日):C++中的函数

第三次修改(咕咕咕):C++的输入与输出流


[前言]C++从C发展而来,它继承了C语言的优点,并引入了面向对象的概念,同时也增加了一些非面向对象的新特性,这些新特性使得C+ +比C更简洁、更安全。

一、C++对于C的一般扩充

[指南]主要介绍了C++ 对C的非面向对象特性的扩展,包括新增的关键字、注释、类型转换、灵活的变量声明、const、struct 、作用域分辨符、 C++ 的动态内存分配、引用、主函数、函数定义、内置函数、缺省参数值、重载函数、 C++的输入输出流等。

1.新增的关键字

C++在C语言的基础上增加了许多关键字

下面是一些常用的关键字:
asm catch class delete friend inline new operator private protected public template virtual try using

2. 注释方式

C语言使用/* 和 */作为注释分界的符号,而C++ 除了保留这种原有的注释模式,还新加了单行注释符号 //

对于这个问题我也感觉到十分诧异,在平时注释的时候并没有发现C不支持单行注释。经过百度后,发现C99才支持单行注释。所以之前的TC是不支持单行注释的。完美解释了我实验课用TC注释一直出错(滑稽)

3. 类型转换

C++ 支持两种不同的类型转换方式

1
2
3
int i=0;
long n=(long)i; //C的类型风格
long m=long(i); //C++的类型风格

C++的这种新风格更像是调用了函数,可读性更好

4.灵活的变量声明

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main()
{
printf("100分!");//不使用变量
int score;//使用变量
score = 100;
printf("是%d分!",score);
return 0;
}

这段代码会因为score在第一个printf后报错

问题根源:编译器问题——C89和C99

C89规定,在任何执行语句之前,在块的开头声明所有局部变量。

在C99以及C++中则没有这个限制,即在首次使用之前,可在块的任何位置都可以声明变量。

我在学习C的过程中接触的应该是C99标准。看到这里我感觉到了C99的强大…还有我是不是要换本书看。讲得太多我好像有点混乱(枯了)

5.const

1. const定义常量

使用const定义常量可以避免define引起的歧义

1
2
3
4
5
6
7
8
9
#include <iostream>
int main()
{
int a=1;
#define T1 a+a
#define T2 T1-T1
cout<<"T2 is "<<T2<<endl;
return 0;
}

输出的是2而不是0

1
2
3
4
5
6
7
8
9
#include <iostream>
int main()
{
int a=1;
const T1 a+a
const T2 T1-T1
cout<<"T2 is "<<T2<<endl;
return 0;
}

输出结果为0

2. const 修饰指针

首先存在 int b=500;

a是一个指向常量的普通指针变量,不是常指针。

1
2
3
4
5
6
7
8
9
const int *a=&b;
int const *a=&b;

//所以a的值是可以改变的
int c=3; //√
a=&c;

//但是对于a指向的内容不能改变
*a=3; //×

指针本身是常量(常指针)而指针所指向的内容不是常量

1
2
3
4
5
6
7
int* const a=&b;

//不能对指针本身进行更改操作
a++; //×

//它指向的数据可以改变
*a=3//√

指针本身和它指向的内容都是常量

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

//指针本身和指向内容都不能修改
a++; //×
*a=3; //×

3. const 在函数中的应用

const 还常用于限定函数的参数和返回值。函数参数如果使用const 声明,则在该函数中不能修改参数。例如:

1
2
3
4
5
float fun(const float x)
{
x=x*x; //非法
return x;
}

如果函数返回基本类型,则用const声明返回值并没有特别的意义,但是如果函数返回一个指针或引用(引用的概念稍后在第九节会讲),则使用const 声明返回值表示调用函数时不能用返回值来改变返回值所指向或引用的变量。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
const int *func()
{
static int x=1;
++x;
return &x;
}
int main()
{
int y;
y=*func(); //合法:将值x传给y
*func()=2; //非法:不能改变常量
return 0;
}

在这个例子中,函数func()的返回值使用了const声明,因此调用func() 函数时不能通过函数返回值来改变它所指向的变量x的值。

6. struct

在C++中,struct后的标识符可直接作为结构体类型名使用,所以定义变量比在C中更加直观。代码如下:

C语言

1
2
3
4
5
6
struct point
{
int x;
int y;
};
struct point p;

C++

1
2
3
4
5
6
struct point
{
int x;
int y;
};
point p;

对于union,也可以照此使用。以上两种方法在C++都适用。

不过我一直在用typedof的写法,现在看起来跟C++的写法差不多

1
2
3
4
5
6
typedof struct Point
{
int x;
int y;
}point;
point p;

7. 作用域分辨运算符::

作用域分辨预算符“::”用于访问当前作用域中被隐藏的数据项。如果有两个重名的变量,一个是全局的,一个是局部的,那么局部变量作用域内具有优先权,同名的全局变量被隐藏无法被访问到。

1
2
3
4
5
6
7
8
9
#include <iostream>
int a=10; //全局变量
int main()
{
int a; //局部变量
a=25;
cout<<a<<endl;
return 0;
}

程序运行结果为25,说明了局部变量的较高优先权

如果希望在局部变量作用域内使用同名的全局变量,则可以在该变量前加上“::”,此时“::a”代表全局变量。

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
int a; //全局变量
int main()
{
int a; //局部变量
a=25;
::a=10;
cout<<"local is "<<a<<endl;
cout<<"global is "<<::a<<endl;
return 0;
}

程序运行结果为local is 25/global is 10

需要注意的是:作用域分辨符只能用来访问全局变量,不能用来访问一个在语句块外声明的同名局部变量。例如,下面代码是错误的:

1
2
3
4
5
6
7
8
9
10
int main()
{
int a; //语句块外局部变量
{
int a=25; //语句块内局部变量
::a=30; //非法
...
}
return 0;
}

8. C++的动态内存分配

在C语言中,动态分配内存是通过调用malloc()和free()来实现的

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <malloc.h> //这里常用stdlib.h 它包含了malloc.h
int main()
{
int *p;
p=(int*)malloc(sizeof(int));
*p=8;
printf("%d",*p);
free(p);
return 0;
}

C++进行动态内存分配使用的是newdelete

运算符new用于内存分配的使用形式为:

==指针变量 = new <数据类型>[<整形表达式>];==

其中<数据类型>可以是基本数据类型、结构等,它表示要分配与<数据类型>相匹配的内存空间;<整形表达式>表示要分配内存单元的个数,默认值为1,可以省略。new运算符返回分配内存单元的起始地址,因此需要把该返回值赋值给一个指针变量。如果当前内存没有足够的空间可以分配,则new运算符返回NULL,并抛出一个运行异常。所以在进行动态内存分配的时候需要检验分配是否成功。

运算符delete用于释放new分配的存储空间,它的使用形式为:

==delete <指针变量>;==

以下是C++中使用新方法进行动态内存分配的例子:

1
2
3
4
5
6
7
8
9
#include <iostream>
int main()
{
int *p=new int; //为指针p分配空间
*p=10;
cout<<*p<<endl;
delete p; //为指针p释放空间
return 0;
}

我们可以看到这种新方法中分配内存时不需要显式地计算int所占用地存储空间大小。

new和delete的一些说明:

  1. 使用new可以为数组分配存储空间,但是需要在类型名后缀上数组的大小。为多维数组分配空间时需要给出每一维的大小,其中第一维的值可以是任何合法的整型表达式。
1
2
3
4
5
6
int *p=new int[10];

int *p=new int[2][3][4];

int i=10;
int *p=new int[i][3][4];
  1. new可以在为简单变量分配内存的同时进行初始化,但是不能对数组进行初始化。
1
2
int *p=new int(99);
//分配了一个整形内存空间并赋值99

3.释放动态分配的数组时可用如下格式:

1
delete []p;

4.建议在使用new分配动态内存时进行检查,避免分配失败引起的程序错误。

9. 引用

引用的定义格式为

==数据类型 &变量名 = 初始值==

引用是一种能够自动间接引用的指针。自动间接引用就是不必使用间接引用运算符“*”就可以得到一个引用值,即指针所指向变量的值。++换句话说,引用就是某一变量的一个别名,对引用的操作就是对变量本身的操作。++

使用规则:

  1. 定义引用时必须立即初始化
1
2
3
int i=5;
int &j; //错误,没有立即初始化
j=i;
  1. 引用不可重新赋值
1
2
3
4
int i=5;
int k=10;
int &j=i;
j=&k; //错误,重新赋值

3.引用不同于普通变量。以下声明是非法的:

1
2
3
int &b[3];  //不能建立引用数组
int &*p; //不能建立指向引用的指针
int &&r; //不能建立指向引用的引用

4.当使用&运算符取一个引用的地址时,其值为所引用变量的地址。

1
2
3
4
int num=50;
int &ref=num;
int *p=&ref;
//p中保存的是变量num的地址

引用作为一般变量几乎没什么意义,最大用处是作为函数形参。

通过两个例子比较能比较清楚地理解引用的意义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//这种方法是地址传递
#include <iostream>
void swap(int *m,int *n)
{
int t;
t=*m;
*m=*n;
*n=t;
}
int main()
{
int a=5,b=10;
cout<<"a= "<<a<<"b= "<<b<<endl;
swap(&a,&b);
cout<<"a= "<<a<<"b= "<<b<<endl;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//这种方法是通过引用传递
#include <iostream>
void swap(int &m,int &n)
{
int t;
t=m;
m=n;
n=t;
}
int main()
{
int a=5,b=10;
cout<<"a= "<<a<<"b= "<<b<<endl;
swap(a,b);
cout<<"a= "<<a<<"b= "<<b<<endl;
return 0;
}

两种方法是等效的,区别就在引用不需要间接引用符“*”。


二、C++中的函数

1. 主函数

C中对于main()函数的格式并无特殊规定,因为C通常不关心返回何种状态给操作系统。

然而,C++要求main()函数匹配下面两种原型之一:

1
2
void main()//无参数,无返回类型
int main(int argc,char *argv[]) //带参数,有返回类型,参数可省略

第二种写法中,形参argc是命令行总的参数个数,argv的元素个数即为argc,其中第0个为可执行文件名,后面是执行所带的参数。

1
2
3
例如,test.exe在命令行执行:
C:\>test a.c b.c
则argc=3 argv[0]="test" argv[1]="a.c" argv[2]="b.c"

如果main()函数前不加返回类型则等价于int main()

2. 函数定义

C++函数定义中的参数说明必须放在函数名后面的括号内,不可将函数的参数说明放在函数说明部分与函数体之间。

1
2
3
void fun(a)
int a;
{ }

但是在C中,这种方法是允许的。

学C的时候完全不知道有这种写法,个人觉得这种写法十分毒瘤,切勿使用。

3. 内置函数

函数调用导致了一定数量的额外开销,如参数入栈出栈等。当函数定义由inline开头时,表明此函数为内置函数。编译时,可使用函数体中的代码来替代函数调用表达式,从而完成与函数调用相同的功能。例如:

1
2
3
4
inline int sum(int a,int b)
{
return a+b;
}

说明:(1)内置函数必须在它被调用之前被定义。
(2)若内置函数较长且调用频繁,编译后程序会加长许多。所以通常只有较短的函数定义为内置函数。

这个内置函数感觉有点像define。函数体太长的话,每个调用的地方都会被替换成一个函数体,相当于多了一段一模一样的代码…

4. 缺省函数值

C++对于C的改进之一就是可以为函数定义缺省参数值。

当函数调用时,编译器按从左向右的顺序将实参和形参结合,若未指定足够的实参,则编译器按顺序用函数原型的缺省值来补足缺少的实参。

1
2
3
4
5
6
int fun(int x=5,int y=10);

fun(1,2); //x=1,y=2
fun(1); //x=1,y=10
fun(); //x=5,y=10
fun(,5) //错误

最后一个调用错误的原因:一个函数可以有多个缺省值,但是缺省参数值必须连续放在后面。不允许出现某个参数值省略后,后一个参数有指定值。

如果缺省值不是连续放在后面,按照从左至右结合可能会出现混乱。但是我有疑惑是,如果我缺省的时候把逗号补全不就让实参形参准确结合了吗?这样fun(,5)就可以算是一种正确的做法,是否还有另外的因素使得不能这样做?

5. 重载函数

在C语言中,函数名必须是唯一的。也就是说,不允许出现同名的函数。如果我要编写一个求不同数据类型的三次方函数,我需要编写三个不同名的函数,同时要在名字标注数据类型的特点。调用时,尽管三个函数的功能相同,我还是需要手动为相应的数据类型调用相应的函数。例如:

1
2
3
Icube(int i);       //求int的三次方
Fcube(float i); //求float的三次方
Dcube(double i); //求double的三次方

而在C++中,我们可以重载函数。只要函数参数类型不同或者参数的个数不同,两个或两个以上的函数就可以使用相同的函数名。==一般而言,重载函数应该实现相同的功能。==所以这能干什么呢?我们在算不同数据类型的三次方时可以调用同一个名字的函数cube(),而它会自动为相应的数据类型调用相应的重载函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
int cube(int i){return i*i*i;}
float cube(float f){return f*f*f;}
double cube(double d){return d*d*d;}
int main()
{
int i=2;
float f=3.4;
double d=5.678;
cout<<i<<"*"<<i<<"*"<<i<<"="<<cube(i)<<endl;
cout<<f<<"*"<<f<<"*"<<f<<"="<<cube(f)<<endl;
cout<<d<<"*"<<d<<"*"<<d<<"="<<cube(d)<<endl;
return 0;
}

注意:重载函数需在参数个数或类型上有所不同,就算返回类型不同,编译程序也不知道调用哪一个重载函数。

1
2
3
这种重载函数是错误的。
int fun(int x,int y);
double fun(int x,int y)

特例:同参数、同参数表的const和非const成员函数可以重载

1
2
int myclass::fun(int a,int b);
int myclass::fun(int a,int b) const;

关于成员函数会在类和对象中学到。

三. C++的输入与输出流

1. C++的流式结构

2. 格式化I/O


从C到C++
https://chanx.tech/2020/a6abb4c98faf/
作者
ischanx
发布于
2020年2月20日
更新于
2023年8月7日
许可协议