C++基础概述
阅读android源码需要对c++基础语法有一定的认识,借此对c++做一个简单的语法认知。
1、数据类型
类型 | 关键字 |
---|---|
布尔型 | bool |
字符型 | char |
整型 | int |
浮点型 | float |
双浮点型 | double |
无类型 | void |
宽字符型 | wchar_t |
一些基本类型可以使用一个或多个类型修饰符进行修饰:
- signed
- unsigned
- short
- long
(1)typedef 声明
使用 typedef 为一个已有的类型取一个新的名字。下面是使用 typedef 定义一个新类型的语法:typedef type newname;
(2)枚举类型
枚举类型(enumeration)是c++中的一种派生数据类型,它是由用户定义的若干枚举常量的集合。
如果一个变量只有几种可能的值,可以定义为枚举(enumeration)类型。所谓"枚举"是指将变量的值一一列举出来,变量的值只能在列举出来的值的范围内。
创建枚举,需要使用关键字 enum。枚举类型的一般形式为:
enum 枚举名{ 标识符[=整型常数], 标识符[=整型常数], ... 标识符[=整型常数] } 枚举变量;
如果枚举没有初始化, 即省掉"=整型常数"时, 则从第一个标识符开始。
默认情况下,第一个名称的值为 0,第二个名称的值为 1,第三个名称的值为 2,以此类推。但是,您也可以给名称赋予一个特殊的值,只需要添加一个初始值即可。例如,在下面的枚举中,green 的值为 5。
enum color { red, green=5, blue };
在这里,blue 的值为 6,因为默认情况下,每个名称都会比它前面一个名称大 1,但 red 的值依然为 0。
(3)变量类型
变量的名称可以由字母、数字和下划线字符组成。它必须以字母或下划线开头。
几种基本变量类型:bool、char、int、float、double、void、wchar_t
c++ 也允许定义各种其他类型的变量,比如枚举、指针、数组、引用、数据结构、类等等。
例如,列出几个有效的声明:
int i, j, k; char c, ch; float f, salary; double d;
extern int d = 3, f = 5; // d 和 f 的声明
int d = 3, f = 5; // 定义并初始化 d 和 f
byte z = 22; // 定义并初始化 z
char x = 'x'; // 变量 x 的值为 'x'
(4)左值(lvalues)和右值(rvalues)
c++ 中有两种类型的表达式:
- 左值(lvalue):指向内存位置的表达式被称为左值(lvalue)表达式。左值可以出现在赋值号的左边或右边。
- 右值(rvalue):术语右值(rvalue)指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式,也就是说,右值可以出现在赋值号的右边,但不能出现在赋值号的左边。
变量是左值,因此可以出现在赋值号的左边。数值型的字面值是右值,因此不能被赋值,不能出现在赋值号的左边。
(5)变量作用域
作用域是程序的一个区域,一般来说有三个地方可以定义变量:
-
在函数或一个代码块内部声明的变量,称为局部变量。
-
在函数参数的定义中声明的变量,称为形式参数。
-
在所有函数外部声明的变量,称为全局变量。
(6)常量
常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量。
常量可以是任何的基本数据类型,可分为整型数字、浮点数字、字符、字符串和布尔值。
整数常量可以是十进制、八进制或十六进制的常量。
浮点常量由整数部分、小数点、小数部分和指数部分组成。可以使用小数形式或者指数形式来表示浮点常量。
当使用小数形式表示时,必须包含整数部分、小数部分,或同时包含两者。当使用指数形式表示时, 必须包含小数点、指数,或同时包含两者。带符号的指数是用 e 或 e 引入的。
例如:
3.14159 // 合法的 314159e-5l // 合法的 510e // 非法的:不完整的指数 210f // 非法的:没有小数或指数 .e55 // 非法的:缺少整数或分数
布尔常量共有两个,它们都是标准的 c++ 关键字:
- true 值代表真。
- false 值代表假。
字符常量是括在单引号中。如果常量以 l(仅当大写时)开头,则表示它是一个宽字符常量(例如 l'x'),此时它必须存储在 wchar_t 类型的变量中。否则,它就是一个窄字符常量(例如 'x'),此时它可以存储在 char 类型的简单变量中。
字符常量可以是一个普通的字符(例如 'x')、一个转义序列(例如 '\t'),或一个通用的字符(例如 '\u02c0')。
在 c++ 中,有一些特定的字符(转义序列码),当它们前面有反斜杠时,它们就具有特殊的含义,被用来表示如换行符(\n)或制表符(\t)等。
字符串字面值或常量是括在双引号 "" 中的。一个字符串包含类似于字符常量的字符:普通的字符、转义序列和通用的字符。
定义常量:在 c++ 中,有两种简单的定义常量的方式:
- 使用 #define 预处理器。>> #define length 10
- 使用 const 关键字。>> const int length = 10;
(7)修饰符类型
c++ 允许在 char、int 和 double 数据类型前放置修饰符。修饰符用于改变基本类型的含义,所以它更能满足各种情境的需求。
下面列出了数据类型修饰符:
- signed
- unsigned
- long
- short
修饰符 signed、unsigned、long 和 short 可应用于整型,signed 和 unsigned 可应用于字符型,long 可应用于双精度型。
修饰符 signed 和 unsigned 也可以作为 long 或 short 修饰符的前缀。例如:unsigned long int。
c++ 允许使用速记符号来声明无符号短整数或无符号长整数。您可以不写 int,只写单词 unsigned、short 或 unsigned、long,int 是隐含的。例如:
unsigned x; unsigned int y;
(8)类型限定符
提供了变量的额外信息:
const | const 类型的对象在程序执行期间不能被修改改变。 |
volatile | 修饰符 volatile 告诉编译器不需要优化volatile声明的变量,让程序可以直接从内存中读取变量。对于一般的变量编译器会对变量进行优化,将内存中的变量值放在寄存器中以加快读写效率。 |
restrict | 由 restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 c99 增加了新的类型限定符 restrict。 |
(9)c++ 存储类
存储类定义 c++ 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前。下面列出 c++ 程序中可用的存储类:
-
static:
指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。
static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。
-
extern:
用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 'extern' 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。
当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。可以这么理解,extern 是用来在另一个文件中声明一个全局变量或函数。
extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候。
- mutable:允许对象的成员替代常量。也就是说,mutable 成员可以通过 const 成员函数修改。
-
thread_local (c++11):
使用 thread_local 说明符声明的变量仅可在它在其上创建的线程*问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。
thread_local 说明符可以与 static 或 extern 合并。
可以将 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; // 本地变量 }
(10)运算符
下类型的运算符:
- 算术运算符
- 关系运算符
- 逻辑运算符
- 位运算符
- 赋值运算符
- 其他运算符:
运算符 描述 sizeof 返回变量的大小。例如,sizeof(a) 将返回 4,其中 a 是整数。 condition ? x : y 。如果 condition 为真 ? 则值为 x : 否则值为 y。 , 会顺序执行一系列运算。整个逗号表达式的值是以逗号分隔的列表中的最后一个表达式的值。 .(点)和 ->(箭头) 用于引用类、结构和共用体的成员。 cast 把一种数据类型转换为另一种数据类型。例如,int(2.2000) 将返回 2。 & 返回变量的地址。例如 &a; 将给出变量的实际地址。 * 指向一个变量。例如,*var; 将指向变量 var。
2、基本语法
(1)循环
循环类型 | 描述 |
---|---|
while 循环 | 当给定条件为真时,重复语句或语句组。它会在执行循环主体之前测试条件。 |
for 循环 | 多次执行一个语句序列,简化管理循环变量的代码。 |
do...while 循环 | 除了它是在循环主体结尾测试条件外,其他与 while 语句类似。 |
嵌套循环 | 您可以在 while、for 或 do..while 循环内使用一个或多个循环。 |
(2)判断
语句 | 描述 |
---|---|
if 语句 | 一个 if 语句 由一个布尔表达式后跟一个或多个语句组成。 |
if...else 语句 | 一个 if 语句 后可跟一个可选的 else 语句,else 语句在布尔表达式为假时执行。 |
嵌套 if 语句 | 您可以在一个 if 或 else if 语句内使用另一个 if 或 else if 语句。 |
switch 语句 | 一个 switch 语句允许测试一个变量等于多个值时的情况。 |
嵌套 switch 语句 | 您可以在一个 switch 语句内使用另一个 switch 语句。 |
(3)函数
一个函数的组成部分:
- 返回类型:一个函数可以返回一个值。return_type 是函数返回的值的数据类型。有些函数执行所需的操作而不返回值,在这种情况下,return_type 是关键字 void。
- 函数名称:这是函数的实际名称。函数名和参数列表一起构成了函数签名。
- 参数:参数就像是占位符。当函数被调用时,您向参数传递一个值,这个值被称为实际参数。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数。
- 函数主体:函数主体包含一组定义函数执行任务的语句。
函数参数:
如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数。
形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。
当调用函数时,向函数传递参数的方式:
调用类型 | 描述 |
---|---|
传值调用 | 该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数对实际参数没有影响。 |
指针调用 | 该方法把参数的地址复制给形式参数。在函数内,该地址用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。 |
引用调用 | 该方法把参数的引用复制给形式参数。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。 |
(4)数组
可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量。
所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素。
例如:double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
(5*)指针
每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。
指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
type *var-name;
(type 是指针的基类型,var-name 是指针变量的名称。星号是用来指定一个变量是指针。)
例如:
int *ip; /* 一个整型的指针 */ double *dp; /* 一个 double 型的指针 */ float *fp; /* 一个浮点型的指针 */ char *ch; /* 一个字符型的指针 */
使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。
例如:
#include <iostream> using namespace std; int main () { int var = 20; // 实际变量的声明 int *ip; // 指针变量的声明 ip = &var; // 在指针变量中存储 var 的地址 cout << "value of var variable: "; cout << var << endl; //20 // 输出在指针变量中存储的地址 cout << "address stored in ip variable: "; cout << ip << endl; //地址 // 访问指针中地址的值 cout << "value of *ip variable: "; cout << *ip << endl; //20 return 0; }
(6)引用&
引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
例如:
int i = 17; int& r = i; //为 i 声明引用变量 double& s = d;
(7)结构
定义结构,必须使用 struct 语句。struct 语句定义了一个包含多个成员的新的数据类型。
例如:
#include <iostream> #include <cstring> using namespace std; // 声明一个结构体类型 books struct books { char title[50]; char author[50]; char subject[100]; int book_id; }; int main( ) { books book1; // 定义结构体类型 books 的变量 book1 books book2; // 定义结构体类型 books 的变量 book2 // book1 详述 strcpy( book1.title, "c++ 教程"); strcpy( book1.author, "runoob"); strcpy( book1.subject, "编程语言"); book1.book_id = 12345; // book2 详述 strcpy( book2.title, "css 教程"); strcpy( book2.author, "runoob"); strcpy( book2.subject, "前端技术"); book2.book_id = 12346; // 输出 book1 信息 cout << "第一本书标题 : " << book1.title <<endl; // 输出 book2 信息 cout << "第二本书标题 : " << book2.title <<endl; return 0; }
可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似,如下所示:
struct books *struct_pointer;
现在,您可以在上述定义的指针变量中存储结构变量的地址。为了查找结构变量的地址,请把 & 运算符放在结构名称的前面,如下所示:
struct_pointer = &book1;
为了使用指向该结构的指针访问结构的成员,您必须使用 -> 运算符,如下所示:
struct_pointer->title;
注:可以使用typedf为结构体定义别名
3、面向对象
(1)继承
格式:
class derived-class: access-specifier base-class
其中,访问修饰符 access-specifier 是 public、protected 或 private 其中的一个,base-class 是之前定义过的某个类的名称。如果未使用访问修饰符 access-specifier,则默认为 private。
派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。
一个派生类继承了所有的基类方法,但下列情况除外:
- 基类的构造函数、析构函数和拷贝构造函数。
- 基类的重载运算符。
- 基类的友元函数。
多继承:
多继承即一个子类可以有多个父类,它继承了多个父类的特性。
c++ 类可以从多个类继承成员,语法如下:
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,… { <派生类类体> };
(2)函数重载
在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。您不能仅通过返回类型的不同来重载函数。
(3)多态
当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
c++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
#include <iostream> using namespace std; class shape { protected: int width, height; public: shape( int a=0, int b=0) { width = a; height = b; } virtual int area() { cout << "parent class area :" <<endl; return 0; } }; class rectangle: public shape{ public: rectangle( int a=0, int b=0):shape(a, b) { } int area () { cout << "rectangle class area :" <<endl; return (width * height); } }; class triangle: public shape{ public: triangle( int a=0, int b=0):shape(a, b) { } int area () { cout << "triangle class area :" <<endl; return (width * height / 2); } }; // 程序的主函数 int main( ) { shape *shape; rectangle rec(10,7); triangle tri(10,5); // 存储矩形的地址 shape = &rec; // 调用矩形的求面积函数 area shape->area(); // 存储三角形的地址 shape = &tri; // 调用三角形的求面积函数 area shape->area(); return 0; }
虚函数:
虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
纯虚函数:
想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。
例如:
class shape { protected: int width, height; public: shape( int a=0, int b=0) { width = a; height = b; } // pure virtual function virtual int area() = 0; };
= 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数。
(4)数据抽象
只向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节。
(5)数据封装
封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。数据封装引申出了另一个重要的 oop 概念,即数据隐藏。
数据封装是一种把数据和操作数据的函数捆绑在一起的机制,数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。
c++ 通过创建类来支持封装和数据隐藏(public、protected、private)。
例如:
class box { public: double getvolume(void) { return length * breadth * height; } private: double length; // 长度 double breadth; // 宽度 double height; // 高度 };
(6)接口(抽象类)
接口描述了类的行为和功能,而不需要完成类的特定实现。
c++ 接口是使用抽象类来实现的,抽象类与数据抽象互不混淆,数据抽象是一个把实现细节与相关的数据分离开的概念。
如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用 "= 0" 来指定的。
设计抽象类(通常称为 abc)的目的,是为了给其他类提供一个可以继承的适当的基类。抽象类不能被用于实例化对象,它只能作为接口使用。
因此,如果一个 abc 的子类需要被实例化,则必须实现每个虚函数,这也意味着 c++ 支持使用 abc 声明接口。如果没有在派生类中重载纯虚函数,就尝试实例化该类的对象,会导致编译错误。
可用于实例化对象的类被称为具体类。