C++基础入门
前言
优秀的C++ 书籍把精力集中于与面向对象模型相关的课题上(如类的设计),而不是专注于基本的C技巧,这样做是对的。但C++ 是建立在C 基础之上的,掌握C 的基本技巧依然非常重要。
C++基础语法
1 初识C++
B语言
1969年,美国贝尔实验室的Ken Thompson。以BCPL语言为基础,设计出很简单且很接近硬件的B语言(取BCPL的首字母)。并且他用 B语言 写了第一个 UNIX 操作系统。
C语言
C语言之父,UNIX之父 丹尼斯·里奇
C语言的发明原因很简单, 就是C语言之父 丹尼斯·里奇 和同事在开发Unix系统时需要更好的语言工具, 当时没有“高级”语言来更多地控制所有涵盖操作系统的数据。所以在开发Unix操作系统的同时,就发明了C语言;
1969-1973年在美国电话电报公司 贝尔实验室开始了C语言的最初研发。根据C语言的发明者丹尼斯·里奇说,C 语言最重要的研发时期是在1972年。
C++ 语言
C++ 语言是在C语言的基础上扩充而来的, 同样也是在贝尔实验室诞生的;
1982年, 本贾尼·斯特劳斯特卢普 发明C++语言;
C++ 之父
这里插一张C++ 语言之父的肖像图在这镇楼
2 数据类型
整型
名称 | 描述 | 大小(字节) |
---|---|---|
short | 短整型 | 2 |
int | 整型 | 4 |
long | 长整型 | win 4字节 / linux 8字节 |
long long | 长长整型 | 8 |
整型大小: short < int <= long < long long
注:
在C++中,八进制数以0开头,如012转换成十进制是10;十六进制数以0x开头,如0x64是十进制的100。
浮点型
名称 | 描述 | 大小(字节) |
---|---|---|
float | 单精度浮点型 | 4 |
double | 双精度浮点型 | 8 |
科学计数法 float f1 = 3e2; // 3* 10^2 = 300 ; float f2 = 3e-2; // 3* 0.1^2 = 0.03;
字符型
名称 | 描述 | 大小(字节) |
---|---|---|
char | 字符型 | 1 |
字符要用单引号,如 char a = 'a'
;
注:
C/C++ 中的字符变量只占用1个字节; 字符型变量并不是把字符本身放到内存中存储, 而是把对应的ASCII编码放到存储单元;
转义字符
\n 换行符
\t 水平制表Tab
\ 代表一个反斜杠”"
**字符串型 **
在C++ 中,有两种风格的字符串写法:
char str[] = "hello word"; //C风格写法
string str = "hello word"; //C++风格写法
注意:
这里的两种写法和java非常相似,但又有不同, 在java中,char str [] 代表数组, 只能跟{ }, 而java的字符串类型是String,C++是string。
布尔类型
名称 | 描述 | 大小(字节) |
---|---|---|
bool | 布尔型 | 1 |
小结
C++ 中的基本类型如下:
short 短整型
int 整型
long 长整型
long long 长长整型
char 字符型
bool 布尔型
数据类型所占的内存空间大小不用刻意去记, c++中可以使用 sizeof 关键字计算数据类型所占内存大小:
sizeof(数据类型/变量)
3 运算符
3.1算数运算符
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
+ | 正号 | +3 | 3 |
- | 负号 | -3 | -3 |
+ | 加 | ||
- | 减 | ||
* | 乘 | ||
/ | 除 | ||
% | 取余 | 10%3 | 1 |
++i | 前置递增 | a=2;b=++a; | a=3;b=3; |
i++ | 后置递增 | a=2;b=a++; | a=3;b=2; |
–i | 前置递减 | a=2;b=–a; | a=1;b=1; |
i– | 后置递减 | a=2;b=a–; | a=1;b=2; |
3.2赋值运算符
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
= | 赋值 | ||
+= | 加等于 | ||
-= | 减等于 | ||
*= | 乘等于 | ||
/= | 除等于 | ||
%= | 模等于 |
3.3比较运算符
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
== | 等于 | ||
!= | 不等于 | ||
> | 大于 | ||
>= | 大于等于 | ||
< | 小于 | ||
<= | 小于等于 |
3.4逻辑运算符
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
|| | 或 | ||
&& | 与 | ||
! | 非 |
4 程序流程结构
C/C++ 中支持3种基本的程序运行结构: 顺序结构, 选择结构, 循环结构
选择结构
if-else 语句
int num = 600;
if(num > 600){
cout << "数量大于600" <<endl;
}else if(num > 300){
}else{
}
switch 语句
int num = 600;
switch(num){
case 100:
cout << "xxx" <<endl;
break;
case 600:
cout << "yyy" <<endl;
break;
default:
cout << "zzz" <<endl;
}
三目运算符
由 ? 和 : 组成的条件判断表达式,问号前面是逻辑表达式,如果为true,返回结果一,否则返回结果二;
int num = 600;
int score = num>600 ? 600 : 0;
循环结构
循环语句有3个,和java没有区别,循环语句配合跳转关键字continue, break 使用;
break 用于跳出 选择结构或者循环结构
while语句
int i = 0;
while(i<10){
i++;
}
while 括号中的表达式成立,才会执行语句;
do while语句
int i = 0;
do{
i++;
}while(i<10)
do while 语句是先执行do 代码块中的语句,再判断while语句;至少执行1遍;
for 循环
for(int i = 0;i<100;i++){
}
for语句有3个表达式
表达式一:初始化语句,只执行一次;
表达式二:循环判断语句,条件满足,执行循环体,否则循环结束;
表达式三:每次循环结束后需要执行的表达式;
如果表达式二省略,即不判断循环条件,那么这是个死循环:
for(;;){
}
死循环中如果遇到 break,一样可以跳出循环体。
continue 和 break
continue 和 break 在循环体中控制流程:
- continue:结束本次循环,开始下一次循环;
- break:结束整个循环体;
goto 灵活跳转
goto FLAG;
cout << "xxx" <<endl;
FLAG:
cout << "zzz" <<endl;
注:
因为 goto 控制逻辑过于灵活,如果在程序中大量使用,代码将会难以调试和阅读,因此在程序中尽量减少使用。
5 数组
一维数组
int array[3] = {1,2,3};
二维数组
二维数组可以表示一个矩阵;
数据类型 数组名 [行数] [列数] ;
//定义方式一:
int array[2][3]; //行数,列数
[0][0] = 1; //第一行,第一列
[0][1] = 2; //第一行,第二列
[0][2] = 3;
[1][0] = 4;
[1][1] = 5;
[1][2] = 6;
//定义方式二:(也是最常用的)
int array2[2][3]={
{1,2,3},
{4,5,6}
};
for(int i=0;i<2;i++){
for(int j=0;j<3;j++){
cout<< array[i][j] <<endl;
}
}
6 函数
c++中的函数和java 还是有一定差别的.
函数的可以分为声明和定义两步来写, 这和变量的声明和初始化是一样的;
如果函数的声明和调用在同一个源文件中, 那么函数的声明必须在调用之前写完,否则程序报错.
这一点限制的还是比较死的; 不同于java, java中的函数编定义位置和调用位置没有限制的这么死;
当然如果在C++中,函数的声明/定义和调用是分文件编写的, 那么在源文件中include头文件,就可以直接调用了.
小结:
C++中函数可以分为声明和定义,在java的普通类中,没有区分声明和定义, 只有直接编写;
C++中函数的声明像极了java中的抽象方法, 只有声明,没有具体实现;
在同一个文件中,如果函数是先声明再定义, 那么声明必须在调用之前编写, 定义的位置没有限制;
如果函数没有声明,直接定义, 那么定义必须在调用之前编写;
java中函数的编写要比C++灵活很多;
函数的声明
int switch0(int num1,int num2);
函数的定义
int switch0(int num1,int num2){
int temp = num1;
num1 = num2;
num2 = temp;
}
函数的分文件编写
函数的分文件编写分2步走:
- 创建.h后缀名的头文件,编写函数的声明;
- 创建.cpp后缀名的源文件, 编写函数的定义;
swap.h 头文件:
//在头文件中声明函数
int switch0(int num1,int num2);
swap.cpp 源文件:
//引入头文件
#include "swap.h"
int switch0(int num1,int num2){
int temp = num1;
num1 = num2;
num2 = temp;
}
参数传递方式
函数参数传递的方式有2种:
- 值传递
- 地址传递
值传递: 形参的值发生改变, 实参不会改变;
地址传递: 形参的值发生改变, 实参也会改变;
7 指针
每个变量都有一个内存地址,使用 & 变量名
可以读取到变量的内存地址:
int a = 10;
cout << "var1 变量的地址:"<< &a << endl;;
基本概念
指针本质上是一个变量,保存的是一个地址;
指针的作用: 可以通过指针直接或间接的访问内存数据;
- 内存地址是从0开始记录的, 一般用十六进制数字表示;
- 可以利用指针变量保存地址;
指针变量的定义和使用
指针定义的语法: 数据类型 指针变量名* ;
变量取址的语法: &变量名;
int a = 10; //定义变量
int* p; //定义指针
p = &a; //变量a取址并让指针p记录该地址
使用指针:
通过解引用的方式来找到指针所指向的内存数据
解引用语法: *** 指针变量名**;
int b = *p; //指针解引用
注:
int* p 中,声明的int 数据类型,不是说明指针 p 是 int 类型,而是指针 p 指向的内存地址存放的数据是int 类型。
指针变量占用固定的空间大小。
指针所占内存空间
32位操作系统下, 指针占用 4个字节;
64位操作系统下, 指针占用 8个字节;
空指针和野指针
空指针: 指针变量指向内存中编号为0的空间.
注意: 空指针指向的内存是不可以访问的!
int* p2 = NULL; //空指针
野指针: 指针变量指向非法的内存空间;
int* p3 = (int*)0x1100; //野指针
注意:
空指针和野指针都不是自己分配的内存,所以都不能访问!
const 修饰指针
const 修饰指针的三种情况:
常量指针
const 直接修饰指针: const int* p ,叫常量指针;
特点:
指针的指向的值不可以改, 指针的指向可以改;
int a = 10;
int b = 20;
const int* p = &a;
*p =20; //错误写法: 指针指向的值不可以改;
p = &b; //正确写法
指针常量
const 直接修饰常量: int* const p ,叫指针常量;
特点:
指针的指向不可以改, 指针的指向的值可以改;
int a = 10;
int b = 20;
int * const p = &a;
*p = 20; //正确写法
p = &b; //错误写法: 指针的指向不可以修改;
全常量指针
指针的指向和指向的值都不可以改!
int a = 10;
int b = 20;
const int * const p = &a;
*p =20; //错误写法
p = &b; //错误写法
技巧:
const 翻译为常量, int* 翻译为指针,所以 const int* p 叫常量指针; int* const p 叫指针常量;
const 修饰谁,谁就不能改;
const int* p 修饰的是*, 那么 *p (指针的解引用的值)就不能改;int* const p修饰的是p, 那么 p (指针的引用)就不能改;
指针运算
int a = 10;
int* p = &a;
指针与目标变量是密切相关的,
不能给指针变量赋值自定义值(如 int* p = 0x1000),给指针变量赋的值必须是合法的内存地址。
指针变量只支持加法和减法,不支持乘法、除法和取余运行;
指针和数组
我们知道可以用数组下标来访问数组元素;
利用指针来访问数组元素;
int arr[10] = {1,2,3,4,5,6,7,8,9,10}; //初始化数组
int a = arr[0]; //使用数组下标访问数组元素
int * p = arr; //数组名就是数组首个元素的地址
int b = *p; //解引用
for(int i =0;i<10;i++){
cout << arr[i] <<endl;
cout << *p <<endl; //指针解引用,找到指向的内存数据;
p++; //指针后移;
}
指针和函数
//值传递
int a =10;
int b =20;
void swap1(int a,int b){
int temp = a;
a = b;
b = temp;
}
swap1(a,b);
cout << a <<endl;
cout << b <<endl; //结果a=10, b=20;
//地址传递
void swap2(int* p1,int* p2){
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
swap2(&a,&b);
cout << a <<endl;
cout << b <<endl; //结果a=20, b=10;
8 结构体
结构体的定义和使用
结构体是自定义的数据类型 ;
使用 struct 关键字来定义结构体.
struct Student{
int id;
string name;
int age;
};
注:
最后的分号不可以省略,定义结构体时,结构体内的属性不可以赋值(如:int age = 18),否则非法报错;定义结构体不会分配内存,创建结构体变量才会分配内存。
2种方式创建结构体变量
//方式一:
Student stu = {1,"wyc",18};
//方式二:
Student stu ;
stu.id = 1;
stu.name = "wyc";
stu.age = 24;
cout << "wyc: " << stu.name << "" << endl;
使用方式一有一个缺点,那就是结构体成员的初始化顺序必须和声明时保持一致,如果一个成员不赋值,那么后面的所有成员都不能赋值;
注:
定义结构体时, struct 关键字不可以省略;
创建结构体时, struct 关键字可以省略;
结构体中可以声明成员函数,但很少这么做,一般只会声明成员变量;
结构体成员的访问权限默认是public,类成员默认是private,这一点是比较大的区别。
结构体成员通常不会额外加访问修饰符,使用默认的public即可;
结构体数组
Student stus[3]{
{1,"wyc",18},
{2,"www",19},
{3,"jack",25}
};
结构体指针
//1.创建结构体变量
Student stu = {1,"wyc",18};
//2.通过指针指向结构体变量
Student* p = &stu;
//3.通过指针访问结构体变量中的数据
int id = p->id;
string name = p->name;
int age = (*p).age;
注:
通过指针访问结构体变量中的数据有两种方式:p->name
和 (*p).name
;它们都等价于结构体变量.属性 stu.name
;
结构体嵌套结构体
struct Student{
int id;
string name;
int age;
};
struct Teacher{
int id;
string name;
int age;
Student stu;
};
int main(){
Teacher teacher;
teacher.id = 001;
teacher.name = "jack";
teacher.age = 39;
teacher.stu.id=020;
teacher.stu.name="wyc";
teacher.stu.age = 24;
}
结构体做函数参数
函数的参数传递方式有2种: 值传递 和 地址传递;
Student stu = {01,"jack",18};
//值传递
void printStudent01(Student stu){
stu.age = 28;
cout << stu.age << endl;
}
//地址传递
void printStudent02(Student* stu){
stu->age = 28;
cout << stu->age << endl;
}
printStudent01(stu);
printStudent02(&stu);
cout << stu.age << endl;
结果同上面的基本数据类型一样, 值传递时, 作为形参的结构体属性发生变化, 实参不会变化;
而地址传递, 形参改变, 实参也会改变;
总结:
如果你不想改变主函数中的数据,用值传递; 反之用地址传递;
联合体
枚举
C++核心编程
1. 内存分区模型
- 代码存储区 : 存放函数体的二进制代码。
- 全局/静态存储区: 全局变量和静态变量的存储是放在一块的. 局部静态变量也会存储在全局/静态区;
- 常量存储区 : 常量存储区 和 全局/静态存储区 的地址很接近;
- 栈区(stack): 由编译器自动分配释放 ,存放函数的形参,局部变量等。其操作方式类似于数据结构中的栈。
- 堆区(heap): 由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
内存分区的意义:
不同区域存放的数据,赋予不同的生命周期, 方便我们灵活编程;
//查看各变量的内存地址:
int a = 10;
int b = 10;
const int c = 10;
const int d = 10;
static int i = 10;
static int j = 10;
const static int m = 10;
const static int n = 10;
string str1 = "www";
string str2 = "www";
const string str3 = "www";
const string str4 = "www";
void test(int x){
int e = 10;
int f = 10;
const int g = 10;
const int h = 10;
}
int main(){
test(3);
system("pause");
return 0;
}
程序运行前
程序编译后, 生成exe的可执行程序, 未执行程序前分为两个区域
代码区:
存放CPU执行的机器指令;
代码区是共享的, 共享的目的是对于频繁被执行的程序, 只需要在内存中有一份代码即可;
代码区是只读的, 防止程序意外的修改它的指令;
全局区:
该区域的数据在程序结束后由操作系统释放;
程序运行后
栈区:
由编译器自动分配和释放, 存放函数的参数值 , 局部变量等;
注意: 栈区的数据在函数执行完毕后自动释放, 所以不要返回局部变量的地址 ;
栈区:
由程序员分配和释放, 若不手动释放, 程序结束时由操作系统回收;
在C++中使用 new 关键字开辟内存, 使用 delete关键字释放内存,
void fun(){
//在栈中分配内存,函数执行完,自动释放内存
int a = 10;
//在堆中分配内存, 返回地址,所以用指针接收; 该内存不会自动释放,需要手动释放;
int* b = new int(10);
//释放内存;
delete b;
}
new 和 delete 关键字
C++中使用 new
关键字在堆区分配内存;
堆区开辟的内存, 都需要手动开辟, 手动释放; 使用 delete
关键字释放内存;
语法 : new 数据类型
new 开辟内存后, 返回该内存的地址,所以要用指针接收;
int* a = new int(10); //在堆中分配整型数据,值为10
delete a; //释放内存用delete;
int* arr = new int [10]; //在堆中开辟数组
delete[] arr; //释放数组要用delete[];
2. 引用
引用的基本使用
作用: 给变量起别名
语法: 数据类型 &别名 = 原名
int a = 10;
//创建引用
int &b = a;
b = 20;
cout << "a:" << a << endl;
cout << "b:" << b << endl;
引用的注意事项
- 引用必须初始化.
- 引用在初始化后, 不可以再改变.
int a = 10;
int b = 10;
//int &c; 语法错误,引用必须初始化;
int &c = a; //引用一旦初始化,就不可以更改;
c = b; //这是赋值操作,不是更改引用;
引用做函数参数
作用: 函数传参时, 可以利用引用让形参修饰实参.
优点: 可以简化指针修改实参.
//值传递
void swap01(int a,int b) {
int temp =a;
a = b;
b = temp;
}
//地址传递
void swap02(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
//引用传递
void swap03(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int a = 10;
int b = 10;
swap01(a,b);
swap02(&a, &b);
swap03(a, b);
return 0;
}
总结:
通过引用参数产生的效果和地址传递是一样的, 引用语法更简单些;
引用做函数返回值
引用是可以作为函数的返回值存在的;
注意: 不要返回局部变量的引用
用法: 函数返回引用时, 给函数赋值, 也就是说函数调用可以作为左值;
//返回局部变量的引用
int& fun1(){
int a = 10;
return a;
}
//返回静态变量的引用
int& fun2(){
static int a = 10;
return a;
}
int main(){
//int &ref1 = fun1(); //不要返回局部变量的引用(指针)!因为函数调用完后内存释放;
int &ref2 = fun2(); //静态变量的引用地址不会释放,可以返回
//这个写法就是本节说的: 函数返回值是引用,可以作为左值;相当于给函数返回的变量引用赋值;
fun2() = 1000;
return 0;
}
引用的本质
在C++中引用的本质是一个指针常量.
int main(){
int a = 10;
int& ref= a; //引用 (本质是一个指针常量) 相当于 int* const ref = &a;
ref = 30; //修改引用的值, a 和 ref 的值都发生改变; 相当于 *ref = 30;
return 0;
}
结论:
引用在C++中是一个语法糖.
在C++中推荐使用引用, 语法简洁方便;
引用的本质是一个指针常量, 指针常量的指针指向不可以改变,所以引用一旦初始化,就不可以更改;
引用所做的指针操作都是编译器帮我们做了;
常量引用
引用的本质是一个指针常量, 那么常量引用就是一个全常量指针;
// int& ref = 10; 语法错误:引用本身需要一个合法的内存空间;
//加上const,语法就对了,这是一个常量引用;相当于 int temp = 10; const int& ref2 = temp;
const int& ref2 = 10; //相当于 const int* const ref2 = 10;
3. 函数高级
函数的参数默认参数
语法:
- 如果函数参数的某个位置有默认值, 那么这个位置往后,从左到右, 必须都要有默认值 ;
- 如果函数声明有默认值, 函数实现的时候就不能有默认值;
函数声明没有默认值, 函数实现有默认值也是可以的, 但同样不能同时出现默认值的情况;
//如果函数参数的某个位置有默认值, 那么这个位置往后,从左到右, 必须都要有默认值; 否则语法报错;
int func01(int a, int b = 10,int c =20);
//函数声明中参数有默认值,函数实现则不能有默认值;语法不会报错,但是运行会报错;
int func01(int a, int b,int c) {
return a + b + c;
}
int main(){
int a = func01(10); //返回值是 10+10+20=40;
int b = func01(10,20); //返回值是 10+20+20=50;
int b = func01(10,20,30); //返回值是 10+20+30=60;
}
说明:
- 不能同时出现默认值的原因很简单, 函数的声明和实现都有参数默认值, 那么就是一种冲突, 编译器不知道应该以哪个为准,所以不允许这样操作;
- 函数声明有参数默认值, 函数实现就不应该有了; 反之,函数实现有参数默认值, 函数声明就不应该有了;
函数的占位参数
C++函数的形参列表中可以有占位参数, 用来做占位, 调用函数时必须填补该位置.
语法: 返回值类型 函数名 (数据类型){...}
//函数占位参数
void func01(int a, int) {
}
//占位参数也可以有默认值
void func02(int a, int = 10) {
}
int main(){
func01(10,10); //函数占位参数必须填补;
func02(10); //有参数默认值则可以不填;
}
说明:
现阶段函数的占位参数存在的意义不大, 后面会用到该技术, 现在只做了解;
函数重载
函数重载的满足条件:
- 同一作用域下;
- 函数名称相同, 参数类型或个数或顺序 不同;
注意:
函数的返回值不作为重载的条件;
int func();
int func(int a);
int func(int a,int b); //个数不同
int func(int a,double b); //类型不同
int func(double a,int b); //顺序不同
double func(double a,int b); //在同一作用域下,语法错误;
函数重载注意事项
- 引用作为函数重载的条件;
- 函数重载遇到默认参数的情况;
int func(int& a);
int func(const int& a);
int main(){
int a = 10;
const int b = 10;
func(a); //调用的是 func(int& a) 函数
func(b); //调用的是 func(const int& a) 函数
func(10); //调用的是 func(const int& a) 函数
}
说明:
首先,引用作为函数重载的条件,是允许的;
在函数调用时, 传入变量,则走普通引用函数,; 传入常量,则走常量引用函数; 传入值,则也走常量引用函数, 因为
int& a = 10;
的语法是错误的, 所以只能走 func(const int& a) 函数;
int func(int a);
int func(int a,int b=10);
int main(){
//func(10); //语法错误:不知道调用哪一个函数;
func(10,20); //肯定调用int func(int a,int b=10);
}
4. 类和对象
C++面向对象的三大特性: ==封装, 继承,多态== ;
封装
封装属性和行为
访问权限
struct 和 class区别
对象的初始化和清理
C++对象模型和this指针
友元
运算符重载
继承
多态
5. 文件操作
STL
README
作者:米开
微信公众号:米开的网络私房菜

版权声明:本文遵循**知识共享许可协议3.0(CC 协议)**: 署名-非商业性使用-相同方式共享 (by-nc-sa)
本文档很大部分内容来自《黑马程序员》提供的C++课程。
2019-12-22 王衍超 第一次修订