C++语法查漏补缺
C++语法查漏补缺
一、C++ 基本语法
1.typedef声明
您可以使用 typedef 为一个已有的类型取一个新的名字。下面是使用 typedef 定义一个新类型的语法:
1 | typedef type newname; |
2、数据类型:枚举类型
枚举类型(enumeration)是C++中的一种派生数据类型,它是由用户定义的若干枚举常量的集合。
如果一个变量只有几种可能的值,可以定义为枚举(enumeration)类型。所谓"枚举"是指将变量的值一一列举出来,变量的值只能在列举出来的值的范围内。
1 | enum 枚举名{ |
如果枚举没有初始化, 即省掉"=整型常数"时, 则从第一个标识符开始。
例如,下面的代码定义了一个颜色枚举,变量 c 的类型为 color。最后,c 被赋值为 “blue”。
1 | enum color {red, green, blue} c; |
默认情况下,第一个名称的值为 0,第二个名称的值为 1,第三个名称的值为 2,以此类推。但是,您也可以给名称赋予一个特殊的值,只需要添加一个初始值即可。例如,在下面的枚举中,green 的值为 5。
1 | enum color {red, green = 5, blue}; |
在这里,blue 的值为 6,因为默认情况下,每个名称都会比它前面一个名称大 1,但 red 的值依然为 0。
3、常量的定义
在C++中,有两种简单的定义常量的方式:
- 使用#define预处理器
#define identifier value
- 使用const关键字
const type variable = value;
1 |
|
4、C++存储类
存储类定义 C++ 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前。下面列出 C++ 程序中可用的存储类:
-
auto
-
register
-
static
-
extern
-
mutable
-
Thread_local(C++11)
从 C++ 11 开始,auto 关键字不再是 C++ 存储类说明符,且 register 关键字被弃用。
1)auto存储类
自 C++ 11 以来,auto 关键字用于两种情况:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。
C98标准中auto关键字用于自动变量的声明,但由于使用极少且多余,在C11中已删除这一用法。
根据初始化表达式自动推断被声明的变量的类型,如:
1 | auto f=3.14; //double |
2)register存储类
register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)
1 | { |
寄存器只用于需要快速访问的变量,比如计数器。还应注意的是,定义 ‘register’ 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制。
3)static存储类
static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。
static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。
在 C++ 中,当 static 用在类数据成员上时,会导致仅有一个该成员的副本被类的所有对象共享。
1 |
|
4)extern存储类
extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 ‘extern’ 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。
当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。可以这么理解,extern 是用来在另一个文件中声明一个全局变量或函数。
extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候,如下所示:
第一个文件:main.cpp:
1 |
|
第二个文件:support.ccp:
1 |
|
在这里,第二个文件中的 extern 关键字用于声明已经在第一个文件 main.cpp 中定义的 count。
5)mutable存储类
mutable 说明符仅适用于类的对象。它允许对象的成员替代常量。也就是说,mutable 成员可以通过 const 成员函数修改。
6)thread_local存储类
使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。
thread_local 说明符可以与 static 或 extern 合并。
可以将 thread_local 仅应用于数据声明和定义,thread_local 不能用于函数声明或定义。
以下演示了可以被声明为 thread_local 的变量:
1 | thread_local int x; // 命名空间下的全局变量 |
5、C++ 数学运算
C++ 内置了丰富的数学函数,可对各种数字进行运算。下表列出了 C++ 中一些有用的内置的数学函数。
为了利用这些函数,您需要引用数学头文件
![](/blog_img/屏幕快照 2019-05-21 下午4.17.03.png)
6、C++ 随机数
在许多情况下,需要生成随机数。关于随机数生成器,有两个相关的函数。一个是 rand(),该函数只返回一个伪随机数。生成随机数之前必须先调用 srand() 函数。
下面是一个关于生成随机数的简单实例。实例中使用了 time() 函数来获取系统时间的秒数,通过调用 rand() 函数来生成随机数:
1 |
|
7、 C++ 传递数组给函数
C++ 中您可以通过指定不带索引的数组名来传递一个指向数组的指针。
C++ 传数组给一个函数,数组类型自动转换为指针类型,因而传的实际是地址。
如果您想要在函数中传递一个一维数组作为参数,您必须以下面三种方式来声明函数形式参数,这三种声明方式的结果是一样的,因为每种方式都会告诉编译器将要接收一个整型指针。同样地,您也可以传递一个多维数组作为形式参数。
1 | //方式一:形式参数是一个指针 |
8、C++ 从函数返回数组
C++ 不允许返回一个完整的数组作为函数的参数。但是,您可以通过指定不带索引的数组名来返回一个指向数组的指针。
如果您想要从函数返回一个一维数组,您必须声明一个返回指针的函数,如下:
1 | int * myFunction(); |
另外,C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。
现在,让我们来看下面的函数,它会生成 10 个随机数,并使用数组来返回它们,具体如下:
1 |
|
9、C++ 字符串
C++ 提供两种类型的字符串表示形式:
- C 风格字符串
- C++ 引入的string类类型
1)C 风格字符串
C 风格的字符串起源于 C 语言,并在 C++ 中继续得到支持。字符串实际上是使用 null 字符 ‘\0’ 终止的一维字符数组。因此,一个以 null 结尾的字符串,包含了组成字符串的字符。
下面的声明和初始化创建了一个 “Hello” 字符串。由于在数组的末尾存储了空字符,所以字符数组的大小比单词 “Hello” 的字符数多一个。
1 | char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; |
根据数组初始化规则,也可以写成:
1 | char greeting[] = "Hello"; |
以下是在内存中的表示:
![](/blog_img/屏幕快照 2019-05-21 下午4.15.13.png)
C++ 中有大量的函数用来操作以 null 结尾的字符串:
![](/blog_img/屏幕快照 2019-05-21 下午4.15.36.png)
2)C++ 中的 String 类
C++ 标准库提供了 string 类类型,支持上述所有的操作,另外还增加了其他更多的功能。我们将学习 C++ 标准库中的这个类,现在让我们先来看看下面这个实例:
1 |
|
10、C++ 指针
所有指针的值的实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
1)C++ Null 指针
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。
1 | int *ptr = NULL; |
2)C++ 指针的算术运算
指针是一个用数值表示的地址。因此,您可以对指针执行算术运算。可以对指针进行四种算术运算:++、–、+、-。
注意:指针的算术运算单位为该指针类型的字节长度
3)C++ 指针 VS 数组
1 |
|
把指针运算符 * 应用到 var 上是完全可以的,但修改 var 的值是非法的。这是因为 var 是一个指向数组开头的常量,不能作为左值。
由于一个数组名对应一个指针常量,只要不改变数组的值,仍然可以用指针形式的表达式。例如,下面是一个有效的语句,把 var[2] 赋值为 500:
1 | *(var + 2) = 500; |
4)C++ 传递指针给函数
C++ 允许您传递指针给函数,只需要简单地声明函数参数为指针类型即可。
1 | void getSeconds(unsigned long *par) |
能接受指针作为参数的函数,也能接受数组作为参数,如下所示:
1 | double getAverage(int *arr, int size) |
5) C++ 从函数返回指针
声明一个返回指针的函数,如下所示:
1 | int * myFunction(); |
另外,C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。
1 |
|
11、C++ 引用
引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
C++之所以增加引用类型, 主要是把它作为函数参数,以扩充函数传递数据的功能。
1)C++ 引用 VS 指针
引用和指针的区别:
- 不存在空引用,引用必须连接到一块合法的内存
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象
- 引用必须在创建时被初始化。指针可以在任何时间被初始化
2)C++ 中创建引用
1 | int i = 17; |
3)C++ 把引用作为参数
swap函数的示例:
1 |
|
4)C++ 把引用作为返回值
通过使用引用来替代指针,会使 C++ 程序更容易阅读和维护。C++ 函数可以返回一个引用,方式与返回一个指针类似。
当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边。
1 |
|
当返回一个引用时,要注意被引用的对象不能超出作用域。所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用。
1 | int& func() { |
12、C++ 日期 & 时间(本部分后续补充)
C++ 标准库没有提供所谓的日期类型。C++ 继承了 C 语言用于日期和时间操作的结构和函数。为了使用日期和时间相关的函数和结构,需要在 C++ 程序中引用
13、C++ 基本的输入输出
C++ 标准库提供了一组丰富的输入/输出功能。C++ 的 I/O 发生在流中,流是字节序列。如果字节流是从设备(如键盘、磁盘驱动器、网络连接等)流向内存,这叫做输入操作。如果字节流是从内存流向设备(如显示屏、打印机、磁盘驱动器、网络连接等),这叫做输出操作。
1)I/O 库头文件
![](/blog_img/屏幕快照 2019-05-21 下午4.12.06.png)
14、C++ 数据结构(结构体)
1)结构体的定义:
1 | struct Books |
结构体的访问采用’.'的方式访问
2)结构作为函数参数
1 | void printBook( struct Books book ) |
3)指向结构的指针
1 | Books Book1; |
二、C++ 面向对象
1、类访问修饰符(public、protected、private)
- 保护成员变量或函数与私有成员十分相似,但有一点不同,保护成员在派生类(即子类)中是可访问的。
- 继承中的特点:
- **public 继承:**基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:public, protected, private
- **protected 继承:**基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:protected, protected, private
- **private 继承:**基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:private, private, private
若继承时不声明public/protected/private,则默认为private
继承的写法:
1 | class A : public/protected/private B{}; |
继承方式 | 基类的public成员 | 基类的protected成员 | 基类的private成员 | 继承引起的访问控制关系变化概括 |
---|---|---|---|---|
public继承 | 仍为public成员 | 仍为protected成员 | 不可见 | 基类的非私有成员在子类的访问属性不变 |
protected继承 | 变为protected成员 | 变为protected成员 | 不可见 | 基类的非私有成员都为子类的保护成员 |
private继承 | 变为private成员 | 变为private成员 | 不可见 | 基类中的非私有成员都称为子类的私有成员 |
2、C++ 拷贝构造函数
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:
- 通过使用另一个同类型的对象来初始化新创建的对象。
- 复制对象把它作为参数传递给函数。
- 复制对象,并从函数返回这个对象。
**如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。**拷贝构造函数的最常见形式如下:
1 | classname (const classname &obj) { |
3、C++ 友元函数
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend,如下所示:
1 | class Box |
声明类 ClassTwo 的所有成员函数作为类 ClassOne 的友元,需要在类 ClassOne 的定义中放置如下声明:
1 | friend class ClassTwo; |
**说明:**因为友元函数没有this指针,则参数要有三种情况:
-
要访问非static成员时,需要对象做参数;
-
要访问static成员或全局变量时,则不需要对象做参数;
-
如果做参数的对象是全局对象,则不需要对象做参数.
-
可以直接调用友元函数,不需要通过对象或指针
4、C++ 内联函数
C++ 内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。
对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。
如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义。如果已定义的函数多于一行,编译器会忽略 inline 限定符。
在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符。
说明:引入内联函数的目的是为了解决程序中函数调用的效率问题,这么说吧,程序在编译器编译的时候,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换,而对于其他的函数,都是在运行时候才被替代。这其实就是个空间代价换时间的i节省。所以内联函数一般都是1-5行的小函数。在使用内联函数时要留神:
- 1.在内联函数内不允许使用循环语句和开关语句;
- 2.内联函数的定义必须出现在内联函数第一次调用之前;
- 3.类结构中所在的类说明内部定义的函数是内联函数。
5、C++ this指针
在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。
友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。
1 |
|
6、C++ 类的静态成员
我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。
静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化。
如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。
静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。
静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建。
三、C++ 高级编程
待更新…