C++0基础教程

还在苦恼找不到C++的教程吗,这篇文章搞定C++基础内容!

前言

C++是一门编译型语言,是面向对象的。C++对语法的要求较高,且记忆量较大,但其运行速度较快,且编译过后是.exe的文件,可以直接运行,因此深受青睐。

目录

  • 前言
  • 准备工作
  • C++源码文件的介绍
    • .cpp文件
    • .h文件
  • 第一个程序
  • 变量
    • 变量类型
    • 变量的声明方法
      • 常量的声明方法
    • string类型
    • 指针
  • printf()函数
  • 运算符
    • 1.算数运算符
    • 2.关系运算符
    • 3.逻辑运算符
    • 4.位运算符
    • 5.赋值运算符
    • 6.杂项运算符
  • 分支结构
    • if语句
    • switch语句
    • condition?true:false判断
  • 循环结构
    • while循环
    • do-while循环
    • for循环
    • 无限循环
      • break语句
      • continue语句
  • 数组
    • 一维数组的声明方法
      • 下标
    • 二维数组的声明方法
    • 小技巧
  • 再看string类型
  • 函数
    • 变量的作用域
      • 全局变量
      • 局部变量
      • 形式参数(形参)
      • 静态全局/局部变量
    • 静态函数
    • 函数的前置声明
  • 结构体
    • 在类的外部定义函数
    • 构造函数和析构函数
    • 友元函数
    • 内联函数
    • this 指针
    • 指向类的指针
    • 类的静态成员
    • 静态成员函数
    • 继承
  • 头文件的使用
    • 公共函数/变量/类的使用
  • 学完了!

准备工作

C++的环境配置较为简单,首先需要一个IDE(集成开发环境)这里建议使用Dev C++作为C++的开发工具(Dev C++具体安装教程参考知乎上的安装教程)Dev C++装好之后,建议新建一个文件夹(最好全英文路径)存放C++的源码。

C++源码文件的介绍

C++的源码文件一般是.cpp(C++源码文件)和.h(头文件)

.cpp文件

cpp是英文C Plus Plus的缩写,最早的系统区分后缀名的大小写,所以当时.cpp文件的后缀是.C。
存放C++源码的文件,与其他语言不同的是,cpp之间无法直接互相调用,只能通过头文件串联起来。

.h文件

头文件(header),用于声明公共函数、变量、类、结构体等。后面会讲到头文件。

第一个程序

hello_world.cpp

#include 
using namespace std;
int main(){
	cout << "Hello World!";
	return 0;
}

接下来一行一行讲:
第一行:#include
这一行用来包含头文件,#include就是包含头文件,用<>括起来的就是表示能在系统中找到的,就是C++自带的头文件,自己创建的头文件要用""括起来。是C++控制台输入输出的标准流。Dev C++编程的时候还可以用万能头,详情请看@一条余弦Cos 的教程,它包含了很多头文件,非常方便。
第二行:using namespace std;是声明命名空间,std就是standard,基础,默认的意思,cout就是在std中声明的。usingnamespace一般一起出现。
这里还涉及到一个重要语法:每行代码最后都要有;{}除了#开头的预处理指令。
第三行:int main(){声明main函数,最后的{和最后一行的}成对出现,代表main函数的代码块范围,也就是括起来的代码都是main函数里的。前面的int代表main函数的返回值是int类型。main函数是C++唯一的程序入口,有main函数的文件可以被编译运行。C++运行时就是执行main函数里的代码。
第四行:cout << "Hello World!";往控制台输出Hello World这串字符。cout就是console+out,就是往控制台输出,与之对应的还有cin(console+in)从控制台获取输入。<<是输出符,代表输出<<后面的变量或字符串。
第五行:return 0;return语句是返回值,是函数结束的语句,执行到这里时不论后面有没有代码,函数都会结束运行。0是返回的值,int是数字类型,所以返回了数字。这行可以省略,但是只有main函数的return可以省略。
第六行:}跟第三行的{成对出现,作用在第三行讲了。

变量

C++变量是一个重要的部分,它储存了很多运行时需要的数据,并且其中的值可以变化。

变量类型

类型 占用空间/字节 取值范围
short 2 -32768~32767
unsigned short 2 0 ~ 65535
int 4 -231 ~ 231-1
unsigned int 4 0 ~ 232-1
long long 8 -263 ~ 263-1
unsigned long long 8 0 ~ 264-1
float 4 3.4E+10-38 ~ 3.4E+1038
double 8 1.7E+10-308 ~ 1.7E+10308
bool 1 true(>=1) 或 fasle(0)
char 1 字符
pointer(指针) 8

变量的声明方法

test.cpp

#include 
using namespace std;
int main(){
	int n = 10;//声明和赋值一次性完成
	int a,b = 30;//一条语句声明多个变量,且可以单独赋值一个变量
	a = 20;//先声明,后赋值
	cout << n << endl << a << endl << b;//endl代表换行符(\n)
	return 0;
}

运行后,控制台输出为

10
20
30

解析:
第四、五、六行为变量的声明和赋值。int代表变量类型,n、a、b是变量名称,变量的命名遵守以下规则:

  1. 不能以数字开头
  2. 不能包含除数字,大小写字母(区分大小写)和‘_’以外的字符
  3. 在该变量的作用域里,不能重名
  4. 不能与C++关键字重名

常量的声明方法

#include 
using namespace std;
int main(){
	const int n = 10;//常量
	cout << n;
	return 0;
}

const关键字表示常量。

string类型

使用string类型时应当包含头文件。string类型是字符串,后边讲到数组时会细讲。

#include 
#include //一个文件可以包含多个头文件
using namespace std;
int main(){
	string n = "Hello World!";//string类型
	cout << n;
	return 0;
}

运行后,控制台输出为

Hello World!

指针

指针是间接访问变量的工具,当指针指向一个变量后,指针和变量虽然用不同的内存空间,但是值同步

int a = 10;
int * p;//*是解引用
p = &a;//&是取地址符

当我修改a的值为20,p也会变成20,同理当我修改p的值为20,a也会变成20

printf()函数

用法:

#include //需要包含这个头文件
using namespace std;
int main(){
	int n = 0;
	printf("一个数字:%d",n);
	return 0;
}

运行后,控制台输出为

一个数字:0

不难看出,printf就是格式化输出(print + format)。

运算符

运算符分为算术运算符,关系运算符,逻辑运算符,位运算符,赋值运算符,杂项运算符。

1.算数运算符

假设a = 10,b = 20

运算符 描述 示例
+ 把两个操作数相加 a+b = 30
- 从第一个操作数中减去第二个操作数 b-a = 10
* 把两个操作数相乘 a*b = 200
/ 用第一个操作数除以第二个操作数 b/a = 2
% 用第一个操作数整除第二个操作数的余数 b%a = 0
++ 自增运算符,整数值增加1 a++ = ++a = 11
自减运算符,整数值减少1 a-- = --a = 9

注:a++ 和 ++a是代码执行顺序上的不同,–a和a–也一样,后面会讲。

2.关系运算符

假设a = 10,b = 20

运算符 描述 示例
== 检查两个操作数的值是否相等,如果相等则条件为真 a==b 为假
!= 检查两个操作数的值是否不相等,如果不相等则条件为真 a!=b 为真
< 检查左操作数的值是否小于右操作数的值,如果是则条件为真 a
检查左操作数的值是否大于右操作数的值,如果是则条件为真 a>b 为假
<= 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真 a<=b 为真
>= 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真 a>=b 为假

3.逻辑运算符

假设A = true,B = false

运算符 描述 示例
&& 称为逻辑与运算符。如果两个操作数都非零(为真),则条件为真 (A&&B)为假
∣∣ 称为逻辑或运算符。如果两个操作数有一个非零(为真),则条件为真 (A∣∣B)为真
称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 !(A&&B)为真

上述&& 与关键字 and 相同,|| 与 or 相同

4.位运算符

假设a = 60,b = 13,现在以二进制表示,则a = 0011 1100,b = 0000 1101

运算符 描述 示例
& 如果1同时存在于两个操作数的同一位中,二进制AND运算符将此位设为1 (a&b)将得到12,即为0000 1100
如果1存在于任意操作数的一位,二进制OR运算符将此位设为1 (a∣b)将得到61,即为0011 1101
^ 如果两个操作数的同一位不相同,二进制XOR运算符将此位设为1 (a^b)将得到49,即为0011 0001
~ 二进制补码运算符是一元运算符,具有"翻转"位效果(即1变为0,0变为1) (~a)将得到-61,即为1100 0011
<< 二进制左移运算符,左操作数的值向左移动右操作数指定的位数 (a<<2)将得到240,即为1111 0000
>> 二进制右移运算符,左操作数的值向右移动右操作数指定的位数。 (a>>2)将得到15,即为0000 1111

Tip:
1<n)

5.赋值运算符

运算符 描述 示例
= 简单的赋值运算符,把右边操作数的值赋给左边操作数 C=A+B将把A+B的值赋给C
+= 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 C+=A相当于C=C+A
-= 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 C-=A相当于C=C-A
*= 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 C=A相当于C=CA
/= 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 C/=A相当于C=C/A
%= 求模且赋值运算符,求两个操作数的模赋值给左边操作数 C%=A相当于C=C%A
<<= 左移且赋值运算符 C<<=2等同于C=C<<2
>>= 右移且赋值运算符 C>>=2等同于C=C>>2
&= 按位与且赋值运算符 C&=2等同于C=C&2
^= 按位异或且赋值运算符 C ^= 2等同于C=C^2
∣= 按位或且赋值运算符 C∣=2等同于C=C∣2

6.杂项运算符

运算符 描述
Condition?X:Y 条件运算符。如果Condition为真?则值为X,否则值为Y。
, 逗号运算符会顺序执行一系列运算。整个逗号表达式的值是以逗号分隔的列表中的最后一个表达式的值。
.(点)和->(箭头) 成员运算符用于引用类、结构和共用体的成员。
Cast 强制转换运算符把一种数据类型转换为另一种数据类型。例如,int(2.2000)或(int)2.2000将返回2。
& 指针运算符&返回变量的地址。例如&a;将给出变量的实际地址。
* 指针运算符*指向一个变量。例如,*var;将指向变量var。

以上摘自:@开箱剁手 的博客 并稍作修改

分支结构

if语句

#include 
using namespace std;
int main(){
	int n;
	cin >> n;//从控制台获取输入,值存在变量n
	if(n > 30){
		cout << "n比30大";
	}else if(n > 20){
		cout << "n比20大";
	}else{
		cout << "n比20小或等于20";
	}
	return 0;
}

当程序开始运行,先获取n的值(用户输入),接着开始if语句的判断:
先执行if后面的表达式,判断真还是假,如果是真,就执行if里面的代码cout << "n比30大";如果是假,就执行下个分支——else if的表达式,判断真还是假,如果是真,就执行else if里面的代码cout << "n比20大";如果是假,就执行下个分支——else里的代码。if后面可以没有else else if

switch语句

#include 
using namespace std;
int main(){
	cout << "1:注册\n2:登录";
	int n;
	cin >> n;//从控制台获取输入,值存在变量n
	switch(n){//switch要求一个数值表达式
		case 1:
			cout << "请输入用户名:";
			......
			break;//一定不要忘了
		case 2:
			cout << "请输入账号和密码,用空格分开:";
			......
			break;//一定不要忘了
		default:
			cout << "输入错误!";
			break;
	}
	return 0;
}

解释:
switch传入了n,如果n == 1就执行case 1:后面的内容,直到break;case 2:也是以此类推;如果所有的case都不执行,则执行defaultdefault是可选的

condition?true:false判断

一般用于返回值,示例:

#include 
using namespace std;
string getSystemName(){//函数的声明,具体后面会讲
	cout << "输入你的系统名称,Windows或Mac" << endl;
	string s;
	cin >> s;
	return s == "Windows" ? "Windows OS":"Mac OS";
}
int main(){
	string system;
	system = getSystemName();
	cout << "你的系统是" << system;
	return 0;
}

解释:
程序运行时,调用getSystemName()函数,函数最后执行了return s == "Windows" ? "Windows OS":"Mac OS";,它等同于

if(s == "Windows") return "Windows OS";
//所有的花括号在里面只有一行代码的时候都可以省略
else return "Mac OS";

循环结构

有时程序的某部分可能要执行多次,就要用到循环结构。

while循环

语法:

while(Condition){
	//codes...
}

解释:
如果Condition为真,则执行花括号里面的代码,然后再次判断Condition是否为真,直到Condition为假
流程:

Created with Raphaël 2.3.0 开始执行while语句 Condition? while执行的代码 执行后面的代码 yes no

示例:

#include 
using namespace std;
int main(){
	int i = 1;
	while(i <= 100){//当i为101的时候将不再输出
		cout << i << endl;//输出i
		i++;//将i增加1
	}//总共会执行100次,也就是输出1~100的所有数字
}

运行后,控制台输出为

1
2
3
4
……
97
98
99
100

也就是1~100的所有数字

do-while循环

语法:

do{
	//codes...
}while(Condition);

解释:
先执行花括号里面的代码,再判断如果Condition为真,则继续执行花括号里面的代码,然后再次判断Condition是否为真,直到Condition为假
也就是说do-while至少执行一次,而while可能一次都不执行,这是它们本质上的区别。
do-while并不常用,只有少数情况要用到。
流程:

Created with Raphaël 2.3.0 开始执行do-while语句 do-while执行的代码 Condition? 执行后面的代码 yes no

示例:

#include 
using namespace std;
int main(){
	int i = 1;
	do{
		cout << i << endl;//输出i
		i++;//将i增加1
	}while(i <= 100);//当i为101的时候将不再输出
	//总共会执行100次,也就是输出1~100的所有数字
}

运行后,控制台输出为

1
2
3
4
……
97
98
99
100

也就是1~100的所有数字

for循环

for循环是最常用,最好用的循环。
语法:

for(code1;condition;code2){
	//codes...
}

解释:
先执行code1(一般是声明一个index),再判断如果Condition为真,则执行花括号里面的代码,然后执行code2(一般配合index实现控制次数)再次判断Condition是否为真,如果Condition为真,则执行花括号里面的代码,然后执行code2……直到Condition为假
也就是说code1执行一次,而code2在每次循环结束时执行一次。
流程:

Created with Raphaël 2.3.0 开始执行for语句 括号里的第一条语句 Condition? for执行的代码 括号里的第二条语句 执行后面的代码 yes no

运行后,控制台输出为

1
2
3
4
……
97
98
99
100

也就是1~100的所有数字

无限循环

每种语句都有无限循环的写法,例如:
示例:

#include 
using namespace std;
int main(){
	while(true){
	}//while形式
	do{
	}while(true)//do-while形式
	for(;true;){
	}//for形式
}

但是,我们的程序除非是病毒,不然不可能是无限循环,所以有了breakcontinue

break语句

用法:
跳出循环,让break后面的(循环内的)代码不再执行,直接执行循环外剩下的代码。
示例:

#include 
using namespace std;
int main(){
	//以for为例
	for(;true;){
		if(//一定条件下){
			break;
		}
	}
}

continue语句

用法:
跳出这次循环,让continue后面的(循环内的)代码暂时不执行,直接执行下一次循环。
示例:

#include 
using namespace std;
int main(){
	//以for为例
	for(int i = 1;i <= 100;i++){
		if(i%2)//非0即为真,所以这句话的意思是if(i%2 == 1)
			continue;//是奇数就跳过这次循环
			//因为只有continue一行代码,所以可以省略{}
		cout << i << endl//输出i
	}
}

运行后,控制台输出为

2
4
6
8
……
94
96
98
100

也就是1~100的所有偶数

数组

C++中有几种批量存储数据的数据结构,数组就是其中之一。数组分为一维,二维,三维等等,它们储存数据的方法其实就是在内存里创造一串连续的内存空间,然后给每个空间分配一个下标。
数组里的每个数据称为元素

一维数组的声明方法

格式:

数据类型 数组名称[数组大小] = {数据,数据...};
							//↑可省略

解释:
数据类型就是上文提到的变量的数据类型;数组名称命名规则跟变量命名规则相同,一般用a(array);方括号里的是数组大小,就是数组里最多能存几个数据;最后的赋值可以省略,这样赋值只能按顺序给每个元素赋值,不能指定元素赋值,这样赋值有一种方法,可以把所有元素初始化为0

int a[10] = {0};//将所有元素初始化为0

原理:
当数组有一个元素被赋值之后,C++会把剩余元素自动赋值为0。

下标

当我们有了一个数组,想要查看里面的数据时:

#include 
using namespace std;
int a[5] = {10,312,20,40,124};
int main(){
	cout << a;
}

C++0基础教程_第1张图片
这啥呀这是?
这个是数组的地址,可我们想要的是里面的数据,怎么办呢?
还记得前面的for循环吗?

#include 
using namespace std;
int a[5] = {10,312,20,40,124};
int main(){
	for(int i = 1;i <= 5;i++){
		cout << a[i] << " ";//注意空格隔开
		//我们可以通过这种方式(数组名称[下标])访问数组中的元素
	}
}

运行一下!C++0基础教程_第2张图片
不对啊,我记得数据明明是{10,312,20,40,124}
其实,这是因为下标越界了,为啥会越界呢?C++的下标是从0开始的,也就是说,我开一个大小为5的数组,下标实际上只有0,1,2,3,4
所以正确的代码应该是:

#include 
using namespace std;
int a[5] = {10,312,20,40,124};
int main(){
	for(int i = 0;i <= 4;i++){//让i从0开始,到4结束
		cout << a[i] << " ";
	}
}

现在我想一个一个输入数据了,咋办?

#include 
using namespace std;
int a[5] = {10,312,20,40,124};
int main(){
	for(int i = 0;i <= 4;i++){
		cin >> a[i];//总共能输入5个数
	}
	for(int i = 0;i <= 4;i++){
		cout << a[i] << " ";
	}
}

二维数组的声明方法

数据类型 数组名称[数组长度][数组宽度] = {{数据,数据...},{数据,数据...}...};
							  		   //↑可省略

解释:
二维数组跟一维基本相同,不同的是创建是要传入长度和宽度。把所有元素初始化为的方法也要改一下,多维数组的原理基本相同。

int a[10] = {{0}};//将所有元素初始化为0

原理:
因为二维数组在计算机眼里就是用一个数组套更多小数组,所以必须要用两层的花括号。
现在我要看二维数组里面的数据了

#include 
using namespace std;
int a[5][5] = {{10,312,20,4,124},{10,35,20,40,124},{10,312,20,40,14},{10,4452,20,40,124},{230,3782,850,40,124}};
int main(){
	for(int i = 0;i <= 4;i++){
		for(int j = 0;j <= 4;j++){
			cout << a[i][j] << " ";//双重循环嵌套,里面的for输出一行,外面的换行
		}
		cout << endl;//每输出一行就要换行
	}
}

这样输出之后就是这样:

10 312 20 4 124
10 35 20 40 124
10 312 20 40 14
10 4452 20 40 124
230 3782 850 40 124

好丑!有没有什么办法让它好看一点呢?有,这就要提到printf了。

#include 
#include 
using namespace std;
int a[5][5] = {{10,312,20,4,124},{10,35,20,40,124},{10,312,20,40,14},{10,4452,20,40,124},{230,3782,850,40,124}};
int main(){
	for(int i = 0;i <= 4;i++){
		for(int j = 0;j <= 4;j++){
			printf("%4d ",a[i][j]);//%d是整数,%4d代表占四个位的整数
		}
		cout << endl;//每输出一行就要换行
	}
}
 10  312   20    4  124
 10   35   20   40  124
 10  312   20   40   14
 10 4452   20   40  124
230 3782  850   40  124

这样就好多了

小技巧

我们可以用一个常量来控制所有数组的大小:

#include 
#include 
using namespace std;
const int maxn = 5;//注意必须是常量,不然编译会报错
#define N 5//另一种方法
int a[maxn][N] = {{10,312,20,4,124},{10,35,20,40,124},{10,312,20,40,14},{10,4452,20,40,124},{230,3782,850,40,124}};
int main(){
	for(int i = 0;i <= 4;i++){
		for(int j = 0;j <= 4;j++){
			printf("%4d ",a[i][j]);
		}
		cout << endl;
	}
}

在创建数组时,我们可以把数组的大下开大1,然后将下标为0的区域舍弃掉不用,只在下标1 ~ 数组大小(例如上面的5可以写成6,然后只用下标1 ~ 5的区域)

#include 
#include 
using namespace std;
const int maxn = 6;
#define N 6
int a[maxn][N] = {{0},{0,10,312,20,4,124},{0,10,35,20,40,124},{0,10,312,20,40,14},{0,10,4452,20,40,124},{0,230,3782,850,40,124}};
int main(){
	for(int i = 1;i <= 5;i++){
		for(int j = 1;j <= 5;j++){
			printf("%4d ",a[i][j]);
		}
		cout << endl;
	}
}

输出:

 10  312   20    4  124
 10   35   20   40  124
 10  312   20   40   14
 10 4452   20   40  124
230 3782  850   40  124

再看string类型

前面提到过string类型,使用string类型时应当包含头文件。string是由C++提供的方便的字符串,在C语言中用的是char类型的数组。string本质上还是一个char数组,但是创建string的时候不需要提供数组长度。
一个简单的凯撒密码加密

//char方法
#include 
using namespace std;
char s[55];
int main(){
	int n;
	cin >> n;//凯撒密码的移动位数
	scanf("%s",s);//原文字符串
	//scanf可以代替cin,但它不支持string类型的输入
	//使用scanf时,由于s是数组,所以不需要加个&,如果是变量的前面要加&,例如&n
	for(int j = 0;j < strlen(s);j++){//获取char数组的长度
		s[j] = (s[j]-'a'+n)%26+'a';
	}
	printf("%s",s);
}
//string方法
#include 
using namespace std;
string s;
int main(){
	int n;
	cin >> n;//凯撒密码的移动位数
	cin >> s;//原文字符串
	for(int j = 0;j < s.length();j++){//获取string的长度
		s[j] = (s[j]-'a'+n)%26+'a';
	}
	printf("%s",s);
}

原理:
char数组和string本质上一样,把他们拆开看每个字符,然后当字符参与计算的是后,字符可以转换为数字,值是字符的ASCII码
ASCII码对照表:

函数

C++的程序入口main()就是一个函数。函数是指可以包含多行代码的代码块,当执行函数的时候,从上往下执行函数的代码。流程:

Created with Raphaël 2.3.0 程序开始运行 main函数的代码 执行函数f() 函数f()的代码 main函数剩余的代码 程序结束运行 yes no

语法:
声明函数

返回值类型 函数名(形参1,形参2...){//形参是可选的
	函数内的代码
}

例如:

#include 
using namespace std;
void func1(){//void是空返回值的意思
	cout << "调用函数func1\n";//\n是换行符
	return;//虽然没有返回值,但还是可以写return
	cout << "这行代码永远不会执行!";
}
void func2(int a){
	cout << "调用函数func2并传入了" << a << endl;
}
int func3(int a,int b){
	cout << "调用函数func3并传入了" << a << "和" << b << endl;
	return a+b;
}
int main(){
	func1();
	func2(2);
	cout << "收到了来自func3的返回值" << func3(2,4);
}

运行后,控制台输出为

调用函数func1
调用函数func2并传入了2
调用函数func3并传入了2和4
收到了来自func3的返回值6

变量的作用域

C++变量按作用域分类,有五种:

  1. 全局变量
  2. 局部变量
  3. 形式参数(前文提到过的形参)
  4. 静态全局变量
  5. 静态局部变量

全局变量

在所有函数外部声明的变量为全局变量。一般是在程序头部进行声明或者定义。全局变量在整个程序的生命周期内都是有效的,在函数中对全局变量的值进行修改可以改变全局变量的值。
全局变量可以被任何函数访问,可以理解为全局变量只要被声明,整个程序中都可以使用。
当定义全局变量的时候,如果没有手动进行初始化,则系统将自动进行初始化,初始化的内容为:

数据类型 默认初始化
int 0
char ‘\0’
float or double 0
pointer(指针) NULL

局部变量

局部变量指在函数中或者一个代码块中声明的变量,局部变量只能在函数内部或者代码块内部中被使用。
当存在全局变量和局部变量同名的时候,在函数内,访问全局变量时会得到局部变量。

形式参数(形参)

只有在定义了形参的函数内部才能访问。

静态全局/局部变量

定义静态变量时应该加上关键字static

static int n = 0;

静态存储区的变量是在程序刚开始的时候就会完成初始化,也是整个程序生命周期的唯一一次初始化。
静态全局变量只能在声明它的cpp中使用,而静态局部变量只能被初始化一次,已经初始化后如果再次初始化会直接无视掉。

静态函数

静态函数就是在函数声明的时候在前边加一个static。静态函数只在声明他的文件中可用,其他文件不可用。
内部函数:如果一个函数只能被本文件中的函数调用,但是不能被其他文件调用,则称为内部函数。定义内部函数只需加static即可。

函数的前置声明

有一种情况,编译器会报错:

#include 
using namespace std;
void func1(){
	cout << "调用函数func1\n";
	func2();
}
void func2(){
	cout << "调用函数func2" << endl;
}
int main(){
	func1();
}

这时,编译器报错了:找不到函数“func2”
怎么办呢?这时,就有了一个新的概念:前置声明
语法:

返回值类型 函数名(形参1,形参2...);//形参是可选的

解释:
这个声明必须要与函数实现的声明一致,相当于告诉C++我定义了一个函数,其他函数调用的时候你就去找这个函数,不要报错。
例子:

#include 
using namespace std;
void func1();
void func2();
void func1(){
	cout << "调用函数func1\n";
	func2();
}
void func2(){
	cout << "调用函数func2" << endl;
}
int main(){
	func1();
}

学到这里,你已经打败了50%的初学者了!

结构体

当我们要储存好多不同类型的变量,但是它们都有相近的意义,可以用结构体来储存
语法:

struct 结构体名称{
	变量类型 变量名称;
	变量类型 变量名称;
	变量类型 变量名称;//多个是可选的,一般都会存至少2个
}结构体变量名,结构体数组名[数组长度];//这些都是可选的
访问时:
cout << 结构体变量名.在结构体内部声明的变量名称;

什么意思呢?看例子:

//codes...
struct man{
	string name;
	int age
}zhangsan,mans[10];//创建了两个结构体的实例
//codes...
int main(){
	//codes...
	zhangsan.name = "zhangsan";
	zhangsan.age = 20
	cout << zhangsan.name << " " << zhangsan.age;
	mans[2] = {"lisi",23};//一次性储存所有数据,顺序跟结构体内的声明顺序一样
	//该声明方法仅在C++11及以上标准可用。
	//codes...
}

结构体其实就是给多个变量提供一个类似“房子”的区域,不同“房子”内的变量虽然名字相同,但是值不同。

在C++中, 用 “类” 来描述 “对象”。"对象"就是一样东西,相似对象都有共同的属性和行为,这些行为被称作“方法”或“函数”。类可以继承,就像家禽里面具体有鸡、鸭、鹅……一样。类就是C++面向对象的性质。类很重要,很多功能,同时也很难。
C++中使用class关键字定义类:

class 类名{
	public:
		//公共的属性,方法等
	protected:
		//只能被自己和子类访问的
	private:
		//只能被自己访问的	
};

解释:
public,protectedprivate都是权限修饰符,所有的变量,方法都必须写在权限修饰符下面。

权限修饰符名称 可以访问的区域
public 所有函数都可以访问
protected 只有本类,友元和子类能访问
private 外部不能访问,只有本类和友元能访问

类名需要按照变量的命名方式命名。结尾的;不能省略。
类的内部可以创建变量,赋值变量和创建方法:

class Human{
	public:
		string tellOthersName() return name;
		int tellOthersAge() return age;
		char tellOthersGender() return gender;
		void setName(string s) name = s;
		void setAge(int a) age = a;
		void setGender(char g) gender = g;
		void eat();//可以声明但不定义,此时无法访问eat()
	private:
		char gender;
		int age;
		string name;
};

想要创建对象,要如下代码:

int main(){
	Human zhangsan;
	zhangsan.setName("zhangsan");//调用了类中的函数
	zhangsan.setGender('M');
	//codes...
}

在类的外部定义函数

我们可以先在类的内部声明函数,再在类的外部定义函数,需要用到作用域操作符::,例如:

class Human{
	public:
		string tellOthersName() return name;
		int tellOthersAge() return age;
		char tellOthersGender() return gender;
		void setName(string s) name = s;
		void setAge(int a) age = a;
		void setGender(char g) gender = g;
		void eat();
	private:
		char gender;
		int age;
		string name;
};
void Human::eat(){//在类的外部定义函数
	cout << name << "吃了点东西";
}
int main(){
	Human zhangsan;
	zhangsan.setName("zhangsan");//调用了类中的函数
	zhangsan.setGender('M');
	//codes...
}

构造函数和析构函数

类在创建/生命周期结束的时候,分别会执行两个不同的函数——构造函数和析构函数,如果没有定义,就不会执行。
构造函数:
还是以Human类举例

class Human{
	public:
		string tellOthersName() return name;
		int tellOthersAge() return age;
		char tellOthersGender() return gender;
		void setName(string s) name = s;
		void setAge(int a) age = a;
		void setGender(char g) gender = g;
		void eat();
		Human(){//这是构造函数
			cout << "一个人类诞生了\n";
		}
	private:
		char gender;
		int age;
		string name;
};
void Human::eat(){//在类的外部定义函数
	cout << name << "吃了点东西";
}
int main(){
	Human zhangsan;
}

运行时输出:

一个人类诞生了

还有带参数的构造函数:

class Human{
	public:
		string tellOthersName() return name;
		int tellOthersAge() return age;
		char tellOthersGender() return gender;
		void setName(string s) name = s;
		void setAge(int a) age = a;
		void setGender(char g) gender = g;
		void eat();
		Human(string n,int a,char g){//这是构造函数
			cout << "一个人类诞生了\n";
			name = n;age = a;gender = g;
		}
	private:
		char gender;
		int age;
		string name;
};
void Human::eat(){//在类的外部定义函数
	cout << name << "吃了点东西";
}
int main(){
	Human zhangsan("zhangsan",18,'M');//要像函数一样传入参数
	zhangsan.eat();
}

运行时输出:

一个人类诞生了
zhangsan吃了点东西

构造参数可以有默认值:

Human(string n,int a,char g = 'M'){//这是构造函数
	cout << "一个人类诞生了\n";
	name = n;age = a;gender = g;
}

有默认值时,如果创建对象的时候不传入有默认值的参数,那么这个参数就是它的默认值。
我们还可以使用初始化列表来初始化字段,语法:

类的构造函数名(参数1,参数2):类的成员变量1(参数1),类的成员变量2(参数2){
													//多个是可选的
	方法(函数)体
}

例如上面的等同于:

class Human{
	public:
		string tellOthersName() return name;
		int tellOthersAge() return age;
		char tellOthersGender() return gender;
		void setName(string s) name = s;
		void setAge(int a) age = a;
		void setGender(char g) gender = g;
		void eat();
		Human(string n,int a,char g):name(n),age(a),gender(g){
			//这是构造函数
			cout << "一个人类诞生了\n";
		}
	private:
		char gender;
		int age;
		string name;
};
void Human::eat(){//在类的外部定义函数
	cout << name << "吃了点东西";
}
int main(){
	Human zhangsan("zhangsan",18,'M');//要像函数一样传入参数
	zhangsan.eat();
}

析构函数:
析构函数在类的生命周期结束时执行,语法:

~类名(){
	析构函数体
}

析构函数不能有返回值和形参。还是以Human类为例:

class Human{
	public:
		string tellOthersName() return name;
		int tellOthersAge() return age;
		char tellOthersGender() return gender;
		void setName(string s) name = s;
		void setAge(int a) age = a;
		void setGender(char g) gender = g;
		void eat();
		void Human(string n,int a,char g):name(n),age(a),gender(g){
			//这是构造函数
			cout << "一个人类诞生了\n";
		}
		~Human(){
			cout << name << "离我们而去了!\n";
		}
	private:
		char gender;
		int age;
		string name;
};
void Human::eat(){//在类的外部定义函数
	cout << name << "吃了点东西\n";
}
int main(){
	Human zhangsan("zhangsan",18,'M');//要像函数一样传入参数
	zhangsan.eat();
}

运行时输出:

一个人类诞生了
zhangsan吃了点东西
zhangsan离我们而去了!

友元函数

类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。

友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。

如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend,例如:

#include 
using namespace std;
class Pen{
	public:
		addLineLen(Line line,int n){
			line.length += n;
		}
}
class Line{
	private:
		int length;
	public:
		Line(int len) : length(len){
			cout << "line被创建\n";
		}
		friend void printLen(Line line);
		friend class Pen;
};
void printLen(Line line){
	cout << "Line的长度是:" << line.length << endl;
}
int main(){
	Line l(10);
	Pen p;
	printLen(l);
	p.addLineLen(l,5);
	printLen(l);
}

运行时输出:

line被创建
Line的长度是:10
Line的长度是:15

内联函数

C++ 内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。

对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。

如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义。如果已定义的函数多于一行,编译器会忽略 inline 限定符。

在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符。
面是一个实例,使用内联函数来返回两个数中的最大值:

#include 
using namespace std;
inline int Max(int x, int y){
   return (x > y)? x : y;
}
// 程序的主函数
int main( ){
   cout << "Max (20,10): " << Max(20,10) << endl;
   cout << "Max (0,200): " << Max(0,200) << endl;
   cout << "Max (100,1010): " << Max(100,1010) << endl;
}

运行时输出:

Max (20,10): 20
Max (0,200): 200
Max (100,1010): 1010

引入内联函数的目的是为了解决程序中函数调用的效率问题:程序在编译器编译的时候,编译器将程序中出现的内联函数的调用用内联函数的函数体进行替换,而对于其他的函数,都是在运行时候才替换,这样要花费更多时间。这其实就是个程序占用空间变大换时间的节省。所以内联函数一般都是1-5行的小函数。在使用内联函数时要注意:

  • 1.在内联函数内不允许使用循环语句和switch语句;
  • 2.内联函数的定义必须出现在内联函数第一次调用之前;

this 指针

在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。

友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。

this指针需要用->来访问

下面的实例有助于更好地理解 this 指针的概念:

​#include <iostream>
using namespace std;
class Box{
   public:
      // 构造函数定义
      Box(double l=2.0, double b=2.0, double h=2.0){
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
      }
      double Volume(){
         return length * breadth * height;
      }
      int compare(Box box){
         return this->Volume() > box.Volume();
      }
   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};
 
int main(void){
   Box Box1(3.3, 1.2, 1.5);    // Declare box1
   Box Box2(8.5, 6.0, 2.0);    // Declare box2
 
   if(Box1.compare(Box2)){
      cout << "Box2 is smaller than Box1" <<endl;
   }else{
      cout << "Box2 is equal to or larger than Box1" <<endl;
   }
}

运行时输出:

Constructor called.
Constructor called.
Box2 is equal to or larger than Box1

指向类的指针

与this指针基本相同,不同的是能够在外部访问。和普通指针的创建方法一样,也需要用->来访问。

#include 
using namespace std;
class Box{
   public:
      // 构造函数定义
      Box(double l=2.0, double b=2.0, double h=2.0){
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
      }
      double Volume(){
         return length * breadth * height;
      }
   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};
 
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 = &Box1;
   // 现在尝试使用成员访问运算符来访问成员
   cout << "Volume of Box1: " << ptrBox->Volume() << endl;
   // 保存第二个对象的地址
   ptrBox = &Box2;
   // 现在尝试使用成员访问运算符来访问成员
   cout << "Volume of Box2: " << ptrBox->Volume() << endl;
}

运行时输出:

Constructor called.
Constructor called.
Volume of Box1: 5.94
Volume of Box2: 102

类的静态成员

我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。

静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化

#include 
using namespace std;
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 = 0;
 
int main(void){
   Box Box1(3.3, 1.2, 1.5);    // 声明 box1
   Box Box2(8.5, 6.0, 2.0);    // 声明 box2
   // 输出对象的总数
   cout << "Total objects: " << Box::objectCount << endl;
   return 0;
}

运行时输出:

Constructor called.
Constructor called.
Total objects: 2

静态成员函数

如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。

静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。

静态成员函数有一个类范围,他们不能访问类的 this 指针。我们可以使用静态成员函数来判断类的某些对象是否已被创建。

静态成员函数与普通成员函数的区别:

  • 静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
  • 普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针。

下面的实例有助于更好地理解静态成员函数的概念:

#include 
using namespace std;
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;
      }
      static int getCount(){
         return objectCount;
      }
   private:
      double length;     // 长度
      double breadth;    // 宽度
      double height;     // 高度
};
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
int main(void){
   // 在创建对象之前输出对象的总数
   cout << "Inital Stage Count: " << Box::getCount() << endl;
   Box Box1(3.3, 1.2, 1.5);    // 声明 box1
   Box Box2(8.5, 6.0, 2.0);    // 声明 box2
   // 在创建对象之后输出对象的总数
   cout << "Final Stage Count: " << Box::getCount() << endl;
}

运行时输出:

​Inital Stage Count: 0
Constructor called.
Constructor called.
Final Stage Count: 2

继承

类的继承是C++中的一个重要特性。语法:

class 子类名 : public 父类名{
	//codes...
};

继承时自动继承父类的publicprotected属性,当创建一个父类对象时,可以把一个子类对象传入。
例如:

#include 
using namespace std;
class Box{
   public:
      // 构造函数定义
      Box(double l=2.0, double b=2.0, double h=2.0){
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
      }
      double Volume(){
         return length * breadth * height;
      }
      int compare(Box box){
         return this->Volume() > box.Volume();
      }
   private:
      double length;     // 长度
      double breadth;    // 宽度
      double height;     // 高度
};
class ColorfulBox : public Box{
	public:
		ColorfulBox(string c,double l=2.0,double b=2.0,double h=2.0):Box(l,b,h),color(c){
		}
		string getColor(){
			return color;
		}
	private:
		string color;
};
int main(void){
   ColorfulBox Box1("red",3.3, 1.2, 1.5);    // 声明 box1
   ColorfulBox Box2("blue",8.5, 6.0, 2.0);    // 声明 box2
   if(Box1.compare(Box2)){
	cout << Box1.getColor() << " Box biger than " << Box2.getColor() << " Box";	
	}else{
		cout << Box1.getColor() << " Box smaller than or equalls to " << Box2.getColor() << " Box";
	}
}

运行时输出:

Constructor called.
Constructor called.
red Box smaller than or equalls to blue Box

子类可以重写父类的函数,只需要函数头一样即可。如果想在父类的函数上追加一点代码,可以像这样:

#include 
using namespace std;
class Box{
   public:
      ...
      double Volume(){
         return length * breadth * height;
      }
      ...
};
class ColorfulBox : public Box{
	public:
		...
		double Volume(){
         Box :: Volume();//先调用父类的函数
         //codes...
      	}
      	...
};
int main(void){
	...
}

可以多重继承,只需要在每个父类之间加上,,权限修饰符必须在每个父类前面。

头文件的使用

C++中,当我们新建了一个项目,为了代码的可阅读性,往往会把各种函数放到不同的cpp文件中,这时就需要.h文件来把.cpp们串联起来。
一个基础的.h文件:

#program once
//codes...
//或
#ifndef HEAD_FILE_NAME
#define HEAD_FILE_NAME
//codes...
#endif

解释:
第一种方法:#program once这种方法有的编译器不支持,Dev C++是支持的。这句话是告诉编译器这个头文件只能被定义一次,后面写公用函数、类等的定义
第二种方法:

#ifndef HEAD_FILE_NAME
#define HEAD_FILE_NAME
//codes...
#endif

这种方法通过一个宏判断是否已经定义过了,没定义过就定义。

公共函数/变量/类的使用

有几种方法,最常用的是这样:
head.h

#program once

int getN();

cpp1.cpp

#include 
#include "head.h"
using namespace std;
int n = 10;
int getN(){
	return n;
}

main.cpp

#include 
#include "head.h"
using namespace std;
int main(){
	cout << "n = " << getN();
}

运行main.cpp,结果:

n = 10

第二种:直接在head里定义(不推荐,报错概率高)
head.h

#program once
int n = 10;
int getN() return n;

main.cpp

#include 
#include "head.h"
using namespace std;
int main(){
	cout << "n = " << getN();
}

第三种:使用exturn关键字:
head.h

#program once
exturn int n;
exturn int getN();

head.cpp

#include 
#include "head.h"
using namespace std;
int n = 10;
int getN() return n;

main.cpp

#include 
#include "head.h"
using namespace std;
int main(){
	cout << "n = " << getN();
}

结果都一样,需要注意第三种方法必须要有一个cpp与头文件名称相同。
关于类,可以直接定义,也可以用exturn关键字在外部定义。

学完了!

C++的基础部分到这里就学完啦!剩下的就是不断练习,学习算法了!
恭喜!

你可能感兴趣的:(C++,c++,开发语言)