本教程旨在提取最精炼、实用的C++知识点,供读者快速学习及本人查阅复习所用,后期会持续更新。
基本语法
#include <iostream>
using namespace std;
// main() 是程序开始执行的地方
int main()
{
cout << "Hello World" << endl; // 输出 Hello World
return 0;
}
- C++ 语言定义了一些头文件,这些头文件包含了程序中必需的或有用的信息。上面这段程序中,包含了头文件 <iostream>(include <>是只在include的目录下查找它的头文件,include ""表示在全盘查找它的头文件)。
- using namespace std; 告诉编译器使用 std 命名空间。
- int main() 是主函数,程序从这里开始执行。
数据类型
-
C++有7种基本的数据类型:
基本数据类型
可以使用signed,unsigned,short,long去修饰:
类型大小
- typedef声明
可以使用 typedef 为一个已有的类型取一个新的名字。例如:
//typedef type newname;
typedef int feet;
feet distance
变量
- 变量定义
//type variable_name = value;
extern int d = 3, f = 5; // d 和 f 的声明
int d = 3, f = 5; // 定义并初始化 d 和 f
byte z = 22; // 定义并初始化 z
char x = 'x'; // 变量 x 的值为 'x'
- 变量声明
可以使用extern关键字在任意地方声明一个变量。
// 变量声明
extern int a, b;
extern float f;
int main ()
{
// 变量定义
int a, b;
float f;
return 0;
}
同样的,函数声明是,提供一个函数名即可,而函数的实际定义则可以在任何地方进行。
// 函数声明
int func();
int main()
{
// 函数调用
int i = func();
}
// 函数定义
int func()
{
return 0;
}
- 变量作用域
1)在函数或一个代码块内部声明的变量,称为局部变量。
2)在函数参数的定义中声明的变量,称为形式参数。
3)在所有函数外部声明的变量,称为全局变量。
注:当局部变量被定义时,系统不会对其初始化,您必须自行对其初始化。定义全局变量时,系统会自动初始化为下列值:
变量初始化
-
常量
常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量。转义字符常量
- define预处理器
下面是使用 #define 预处理器定义常量的形式:
#define LENGTH 10
#define WIDTH 5
#define NEWLINE '\n'
- const关键字
可以使用 const 前缀声明指定类型的常量,如下所示:
const int LENGTH = 10;
const int WIDTH = 5;
const char NEWLINE = '\n';
运算符
算数运算符
关系运算符
逻辑运算符
位运算符
赋值运算符
杂项运算符
运算符优先级
循环
一如常见的for,while,do while,可以用continue,break,goto来控制,不再赘述。
- 无线循环
for( ; ; )
{
printf("This loop will run forever.\n");
}
判断
if...else if...else,switch等常见套路。
- switch的用法如下:
#include <iostream>
using namespace std;
int main ()
{
// 局部变量声明
char grade = 'D';
switch(grade)
{
case 'A' :
cout << "很棒!" << endl;
break;
case 'B' :
case 'C' :
cout << "做得好" << endl;
break;
case 'D' :
cout << "您通过了" << endl;
break;
case 'F' :
cout << "最好再试一下" << endl;
break;
default :
cout << "无效的成绩" << endl;
}
cout << "您的成绩是 " << grade << endl;
return 0;
}
- 条件运算符?:
Exp1 ? Exp2 : Exp3;
? 表达式的值是由 Exp1 决定的。如果 Exp1 为真,则计算 Exp2 的值,结果即为整个 ? 表达式的值。如果 Exp1 为假,则计算 Exp3 的值,结果即为整个 ? 表达式的值。
函数
- 函数定义
return_type function_name( parameter list )
{
body of the function
}
// 示例:函数返回两个数中较大的那个数
int max(int num1, int num2)
{
// 局部变量声明
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}
- 函数声明
return_type function_name( parameter list );
//示例
int max(int num1, int num2);
//在函数声明中,参数的名称并不重要,只有参数的类型是必需的,因此下面也是有效的声明:
int max(int, int);
注:当你在一个源文件中定义函数且在另一个文件中调用函数时,函数声明是必需的。在这种情况下,您应该在调用函数的文件顶部声明函数。
- 函数参数
如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数。形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。
当调用函数时,有多种向函数传递参数的方式:参数调用
1)传值调用
默认情况下,C++ 使用传值调用来传递参数。
2)指针调用
把参数的地址复制给形参
#include <iostream>
using namespace std;
// 函数定义
void swap(int *x, int *y)
{
int temp;
temp = *x; /* 保存地址 x 的值 */
*x = *y; /* 把 y 赋值给 x */
*y = temp; /* 把 x 赋值给 y */
return;
}
int main ()
{
// 局部变量声明
int a = 100;
int b = 200;
cout << "交换前,a 的值:" << a << endl;
cout << "交换前,b 的值:" << b << endl;
/* 调用函数来交换值
* &a 表示指向 a 的指针,即变量 a 的地址
* &b 表示指向 b 的指针,即变量 b 的地址
*/
swap(&a, &b);
cout << "交换后,a 的值:" << a << endl;
cout << "交换后,b 的值:" << b << endl;
return 0;
}
其中,&a、&b是指变量的地址,swap函数的形参*x、*y中的*是指从x、y的地址取值。(即实参为地址,形参通过指针引用)*
3)引用调用
#include <iostream>
using namespace std;
// 函数定义
void swap(int &x, int &y)
{
int temp;
temp = x; /* 保存地址 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 x 赋值给 y */
return;
}
int main ()
{
// 局部变量声明
int a = 100;
int b = 200;
cout << "交换前,a 的值:" << a << endl;
cout << "交换前,b 的值:" << b << endl;
/* 调用函数来交换值 */
swap(a, b);
cout << "交换后,a 的值:" << a << endl;
cout << "交换后,b 的值:" << b << endl;
return 0;
}
实参为引用,形参通过加&引用实参(区别于传值引用)
- 参数默认值
int sum(int a, int b=20)
{
int result;
result = a + b;
return (result);
}
//调用的时候可以不传入b
sum(a);
数组
- 数组声明
type arrayName [ arraySize ];
double balance[3] = {1000.0,20.0,30.0};
//如果省略掉了数组的大小,数组的大小则为初始化时元素的个数
double balance[] = {1000.0,20.0,30.0};
//数组元素可以通过数组名称加索引进行访问
double salary = balance[0];
- 多维数组
type name[size1][size2]...[sizeN];
//例子
int threedim[5][10][4];
int a[3][4] = {
{0, 1, 2, 3} , /* 初始化索引号为 0 的行 */
{4, 5, 6, 7} , /* 初始化索引号为 1 的行 */
{8, 9, 10, 11} /* 初始化索引号为 2 的行 */
};
int val = a[2][3];
- 指向数组的指针
double *p;
double balance[10];
p = balance;
balance 是一个指向 &balance[0] 的指针,即数组 balance 的第一个元素的地址。因此,上面的程序把 p 赋值为 balance 的第一个元素的地址,通过*p的方式即可访问到balance[0]的值。
- 传递数组给函数
//方式1
void myFunction(int *param)
{
.
int i = *param;
int j = *(param + 1);
.
}
//方式2
void myFunction(int param[10])
{
.
.
.
}
//方式3
void myFunction(int param[])
{
.
int i = param[0];
.
}
- 从函数返回数组
C++ 不允许返回一个完整的数组作为函数的参数。但是,可以通过指定不带索引的数组名来返回一个指向数组的指针。
#include <iostream>
#include <cstdlib>
#include <ctime>
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 << ") : ";
cout << *(p + i) << endl;
}
return 0;
}
字符串
//C风格的字符串
char greeting[] = "Hello";
字符操作函数
//C++中的String类
string str1 = "Hello";
指针
- 指针定义
指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。指针变量声明的一般形式为:
type *var-name;
int *ip; /* 一个整型的指针 */
所有指针的值的实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
- 指针的使用
使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值。
#include <iostream>
using namespace std;
int main ()
{
int var = 20; // 实际变量的声明
int *ip; // 指针变量的声明
ip = &var; // 在指针变量中存储 var 的地址
cout << "Value of var variable: ";
cout << var << endl;
// 输出在指针变量中存储的地址
cout << "Address stored in ip variable: ";
cout << ip << endl;
// 访问指针中地址的值
cout << "Value of *ip variable: ";
cout << *ip << endl;
return 0;
}
其结果为:
Value of var variable: 20
Address stored in ip variable: 0xbfc601ac
Value of *ip variable: 20
- 常用指针操作
//空指针
int *ptr = NULL;
cout << "ptr 的值是 " << ptr ; //结果是:ptr 的值是 0
//指针递增
int var[3] = {10, 100, 200};
ptr = var; //数组的变量名代表指向第一个元素的指针
ptr++;
//指向指针的指针
int var;
int *ptr;
int **pptr;
var = 3000;
// 获取 var 的地址
ptr = &var;
// 使用运算符 & 获取 ptr 的地址
pptr = &ptr;
引用
引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
- 引用很容易与指针混淆,它们之间有三个主要的不同:
1)不存在空引用。引用必须连接到一块合法的内存。
2)一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
3)引用必须在创建时被初始化。指针可以在任何时间被初始化。
// 声明简单的变量
int i;
double d;
// 声明引用变量
int& r = i;
double& s = d;
- 把引用作为返回值
当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边。
#include <iostream>
using namespace std;
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
double& setValues( int i )
{
return vals[i]; // 返回第 i 个元素的引用
}
// 要调用上面定义函数的主函数
int main ()
{
cout << "改变前的值" << endl;
for ( int i = 0; i < 5; i++ )
{
cout << "vals[" << i << "] = ";
cout << vals[i] << endl;
}
setValues(1) = 20.23; // 改变第 2 个元素
setValues(3) = 70.8; // 改变第 4 个元素
cout << "改变后的值" << endl;
for ( int i = 0; i < 5; i++ )
{
cout << "vals[" << i << "] = ";
cout << 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.8
vals[4] = 50
当返回一个引用时,要注意被引用的对象不能超出作用域。所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用。
int& func() {
int q;
//! return q; // 在编译时发生错误
static int x;
return x; // 安全,x 在函数作用域外依然是有效的
}
日期和时间
C++ 标准库没有提供所谓的日期类型。C++ 继承了 C 语言用于日期和时间操作的结构和函数。为了使用日期和时间相关的函数和结构,需要在 C++ 程序中引用 <ctime> 头文件。
- 时间相关的类型
有四个与时间相关的类型:clock_t、time_t、size_t 和 tm。
类型 clock_t、size_t 和 time_t 能够把系统时间和日期表示为某种整数。
结构类型 tm 把日期和时间以 C 结构的形式保存,tm 结构的定义如下:
struct tm {
int tm_sec; // 秒,正常范围从 0 到 59,但允许至 61
int tm_min; // 分,范围从 0 到 59
int tm_hour; // 小时,范围从 0 到 23
int tm_mday; // 一月中的第几天,范围从 1 到 31
int tm_mon; // 月,范围从 0 到 11
int tm_year; // 自 1900 年起的年数
int tm_wday; // 一周中的第几天,范围从 0 到 6,从星期日算起
int tm_yday; // 一年中的第几天,范围从 0 到 365,从 1 月 1 日算起
int tm_isdst; // 夏令时
}
-
关于日期和时间的重要函数
时间函数
基本的输入输出
-
I/O库头文件
头文件
- 标准输出流(cout)
cout << "Value of str is : " << str << endl;
其中,流插入运算符 << 在一个语句中可以多次使用,例如endl 用于在行末添加一个换行符。
- 标准输入流(cin)
#include <iostream>
using namespace std;
int main( )
{
char name[50];
cout << "请输入您的名称: ";
cin >> name;
cout << "您的名称是: " << name << endl;
}
流提取运算符 >> 在一个语句中可以多次使用,如果要求输入多个数据,可以使用如下语句:
cin >> name >> age;
- 标准错误流(cerr)
cerr 对象是非缓冲的,且每个流插入到 cerr 都会立即输出。
cerr << "Error message : " << str << endl;
- 标准日志流
clog 对象是缓冲的。这意味着每个流插入到 clog 都会先存储在缓冲在,直到缓冲填满或者缓冲区刷新时才会输出。
clog << "Error message : " << str << endl;
编写和执行大型程序时,使用 cerr 流来显示错误消息,而其他的日志消息则使用 clog 流来输出。
数据结构
- 定义结构
struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下:
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book;
在结构定义的末尾,最后一个分号之前,可以指定一个或多个结构变量,这是可选的。上面是声明一个结构体类型 Books,变量为 book。
- 访问结构成员
为了访问结构的成员,我们使用成员访问运算符(.)。
Books Book1; // 定义结构体类型 Books 的变量 Book1
// Book1 详述
strcpy( Book1.title, "C++ 教程");
strcpy( Book1.author, "Runoob");
strcpy( Book1.subject, "编程语言");
Book1.book_id = 12345;
- 结构作为函数参数
#include <iostream>
#include <cstring>
using namespace std;
void printBook( struct Books book );
// 声明一个结构体类型 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 信息
printBook( Book1 );
// 输出 Book2 信息
printBook( Book2 );
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;
}
- 指向结构的指针
定义指向结构的指针,方式与定义指向其他类型变量的指针相似。为了使用指向该结构的指针访问结构的成员,必须使用 -> 运算符,如下所示:
struct Books *struct_pointer;
struct_pointer = &Book1;
struct_pointer->title;
- typedef关键字
typedef可以为创建的类型取一个"别名",例如:
typedef struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
}Books;
//现在即可直接使用 Books 来定义 Books 类型的变量
Books Book1, Book2;
typedef long int *pint32;
//x, y 和 z 都是指向长整型 long int 的指针。
pint32 x, y, z;
类和对象
- 类的定义
class Box
{
public:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
double getVolume(void);// 返回体积
};
- 类的声明
Box box1;
Box box2 = Box(parameters);
Box box3(parameters);
Box* box4 = new Box(parameters);
- 访问类的成员
box1.length = 5.0;
cout << box1.length << endl;
- 类成员函数
成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义。
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(void)
{
return length * breadth * height;
}
};
//您也可以在类的外部使用范围解析运算符 :: 定义该函数
double Box::getVolume(void)
{
return length * breadth * height;
}
//调用成员函数同样是在对象上使用点运算符(.)
Box myBox; // 创建一个对象
myBox.getVolume(); // 调用该对象的成员函数
- 类访问修饰符
数据封装是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员。类成员的访问限制是通过在类主体内部对各个区域标记 public、private、protected 来指定的。关键字 public、private、protected 称为访问修饰符。
class Base {
public:
// 公有成员
protected:
// 受保护成员
private:
// 私有成员
};
1)公有成员在程序中类的外部是可访问的。
2)私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。如果没有使用任何访问修饰符,类的成员将被假定为私有成员
3)保护成员变量或函数与私有成员十分相似,但有一点不同,保护成员在派生类(即子类)中是可访问的。
- 类的构造函数和析构函数
1)类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。
2)类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 这是构造函数
Line(double len); // 这是带参数的构造函数
~Line(); // 这是析构函数声明
private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line(void)
{
cout << "Object is being created" << endl;
}
Line::Line( double len)
{
cout << "Object is being created, length = " << len << endl;
length = len;
}
Line::~Line(void)
{
cout << "Object is being deleted" << endl;
delete ptr;
}
- 拷贝构造函数
拷贝构造函数是一种特殊的构造函数,通常用于:通过使用另一个同类型的对象来初始化新创建的对象。
class Line
{
public:
int getLength( void );
Line( int len ); // 简单的构造函数
Line( const Line &obj); // 拷贝构造函数
~Line(); // 析构函数
private:
int *ptr;
};
Line::Line(const Line &obj)
{
cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
ptr = new int;
*ptr = *obj.ptr; // 拷贝值
}
- 友元函数
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
class Box
{
double width;
public:
friend void printWidth( Box box );
void setWidth( double wid );
};
// 请注意:printWidth() 不是任何类的成员函数
void printWidth( Box box )
{
/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
cout << "Width of box : " << box.width <<endl;
}
- 内联函数
当函数被声明为内联函数之后,编译器会将其内联展开,而不是按通常的函数调用机制进行调用。引入内联函数的目的是为了解决程序中函数调用的效率问题,程序在编译器编译的时候,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换,而对于其他的函数,都是在运行时候才被替代。这其实就是个空间代价换时间的i节省。所以内联函数一般都是10行以下的小函数,如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义。内联函数注意点.png
注:在类中定义的成员函数全部默认为内联函数,在类中声明,但在类外定义的为普通函数。 - this指针
在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。
class Box{
public:
Box(){;}
~Box(){;}
Box* get_address() //得到this的地址
{
return this;
}
double Volume()
{
return length * breadth * height;
}
int compare(Box box)
{
//指针通过->访问类成员,对象通过.访问类成员
return this->Volume() > box.Volume();
}
};
注:友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。
- 指向类的指针
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // Declare box1
Box Box2(8.5, 6.0, 2.0); // Declare box2
Box *ptrBox; // Declare pointer to a class.
// 其中ptrBox为地址,*表示从其地址取值
// 保存第一个对象的地址
ptrBox = &Box1;
// 现在尝试使用成员访问运算符来访问成员
cout << "Volume of Box1: " << ptrBox->Volume() << endl;
// 保存第二个对象的地址
ptrBox = &Box2;
// 现在尝试使用成员访问运算符来访问成员
cout << "Volume of Box2: " << ptrBox->Volume() << endl;
return 0;
}
- 类的静态成员
我们可以使用 static 关键字来把类成员定义为静态的。静态成员在类的所有对象中是共享的,当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。
注:如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化
class Box
{
public:
static int objectCount;
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
// 每次创建对象时增加 1
objectCount++;
}
double Volume()
{
return length * breadth * height;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
// 初始化类 Box 的静态成员
int Box::objectCount = 1;
如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。
静态与普通成员函数的区别
继承