持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第13天,点击查看活动详情
在大二的时候学过一段时间C++的语法,但当时学的很模棱两可,再加上现在已经忘得差不多了,所以最近再跟着菜鸟教程过一遍,每篇博客的开始我都会列出这章的所讲内容,因为之前有C语言的基础再加上好多语法已经过完一遍了,所以博客内容只记录了我忘记的或没学过的,根本目的是自用,如果能帮助到您万分荣幸。
序章
- 对象:具有状态和行为。例:狗的状态:颜色,品种.....,行为:吃,跑....。对象是类的实例
- 类:描述对象的行为或状态的模板/蓝图
- 方法:一个方法表示一种行为,一个类可以包含多个方法。可以在方法里写入逻辑,操作数据以及执行所有的动作
- 即时变量:每个对象独特的即时变量,对象的状态是由这些即时变量的值创建的
#include<iostream>
using namespace std;
int main() {
cout << "haaha";
return 0;
}
对于上面的代码:
- C++ 语言定义了一些头文件,这些头文件包含了程序中必需的或有用的信息。上面这段程序中,包含了头文件 。
- using namespace std:告诉编译器使用std命名空间
数据类型
- typedef声明:使用typedef为一个已有的类型取一个新的名字
typedef int haha;//haha是int的另一个名字
haha a;//创建一个int型变量a
枚举类型
- 之前学习的时候没有接触过,大概意思就是自己定义一个数据类型。服务对象:一个变量有几种可能的值。
enum 自定义的数据类型{
枚举1,
枚举2,
枚举3
}枚举变量名;
enum Color{
red,
green,
blue
}c;
- 如果像上面这样枚举的值没给出初始化,就默认从0开始,依次加一,即red=0;green=1;blue=2
- 如果把上面的green=6,则red=1,green=6,blue=7;
变量声明
- 作用:就是使用多个文件的时候,我只在其中一个文件里定义了这个变量,那么变量声明就让编译器知道你这个变量是定义过的,在编译器不需要知道变量完整细节就可以进一步编译。
- 声明方法:用extern声明
//.h文件
#include <iostream>
using namespace std;
int a=10, b=20, c=30;
//源文件
#include <iostream>
#include"l.h"
using namespace std;
extern int a, b;//声明变量
extern int c;
int main()
{
cout << c+a << endl;
return 0;
}
//输出结果为40.
左值和右值
- 左值就是指向内存位置的表达式,可以出现在赋值号的左边或右边
- 右值就是存储在内存中某些地址的数值,右值只能出现在赋值号的右边
常量之定义常量
在c++中,有两种定义常量的方式
//#define 预处理器
#define a 100
//const 关键字
const int a = 6;
修饰符类型
修饰符就是在int,char,double这些数据类型前面加一些东西,加什么东西呢?下面列出数据类型修饰符
- signed(有符号)
- unsigned(无符号)
- long
- short
那这些数据类型修饰符有没有什么使用限制呢?
- int型:可以使用全部的数据修饰符
- char型:只能 使用signed和unsigned
- double型:只能使用long型
signed和unsigned也可以作为long或short的修饰符
signed long int a;
unsigned short int b;
类型限定符
| const | const类型的对象在程序执行期间不能被改变 |
|---|---|
| volatile | 告诉编译器不需要优化volatile类型的变量,直接从内存中读取变量。 |
| restrict | restrict修饰的指针是唯一一种访问它指向的对象的方式 |
存储类
存储类的作用:定义变量或函数的范围和生命周期,这些说明符放在所修饰类型之前,C++程序的可用存储类一共就六个
auto存储类(从 C++ 17 开始,auto 关键字不再是 C++ 存储类说明符)
- auoto声明变量时根据变量初始化表达式自动判断变量类型
- 声明函数时函数返回值的占位符
- auto定义局部变量,离开作用域会被自动释放。
auto a =1.2;
auto s = ("hello");
register存储类
- 功能:定义存储在寄存器中而不是RAM中的局部变量
- 变量的最大尺寸即寄存器的大小
- 寄存器:用于需要快速访问的变量,eg:计数器。
- 注意:使用register并不意味着变量将被存储在寄存器中,只是意味着可能存储在寄存器中,这取决于硬件条件
{
register int m;
}
static存储类
- 编译器在程序的生命周期内保持局部变量的存在,不需要每次进入作用域后进行创建和销毁
- 即:用static修饰局部变量可以在函数调用之间保持局部变量的值
static int count =10;
extern存储类
- 功能:提供一个全局变量的引用,全局变量对所有的程序文件都是可见的
mutable
thread_local(C++11)
位运算符
| 符号 | 描述 | 运算规则 | |
|---|---|---|---|
| & | 与 | 两个位都是1,结果才是1 | |
| 或 | 两位有一个是1,结果就是1 | ||
| 异或 | 两个位相同是0,相异是1 | ||
| ~ | 取反 | 0变1,1变0 | |
| << | 左移 | 每个位左移,高位丢弃,低位补0 | |
| >> | 右移 | 每个位右移,低位丢弃,高位正数左补0,负数左补1 |
逗号运算符
- 作用:执行一系列用逗号隔开的运算
#include <iostream>
using namespace std;
int main()
{
int i = 10, a = 20, b = 30;
i = b, a;
cout << i;
i = (b, a);
cout << i;
return 0;
}
//输出为30,20
无限循环
for( ; ; )
#include <iostream>
using namespace std;
int main()
{
for (; ; ) {
cout << "这是一个无限循环";
}
return 0;
}
//可以按Ctrl C停止循环
判断之switch语句
每次学每次都忘,可能是因为自己不常用吧,再拿来记一遍。
基本格式:
switch(表达式)//直接写表达式 不用双引号
{
case 常量表达式1 :语句/程序块1;break;
case 常量表达式2 :语句/程序块2;break;
…….
case 常量表达式n :语句/程序块n;break;
Default:语句/程序块n+1//上述case没有符合的,执行default
}
必须用break跳出每一个case,不然好会一直执行
函数之函数声明
int max(int a ,int b);
参数的名称并不重要,类型是必须的,因此也可以写成
int max(int int);
一些有用的c++内置数学函数
需要引用数学头文件 cmath
| 函数 | 描述 |
|---|---|
| sin( ) | 返回弧度角(double型)的正弦 |
| cos( ) | 返回弧度角的余弦 |
| tan( ) | 返回弧度角的正切 |
| log( ) | 返回参数的自然对数(ln) |
| pow(x,y) | 返回x的y次方 |
| sqrt ( ) | 返回参数的平方根 |
| hypot(x,y) | 假设x,y分别是两个直角边,函数返回斜边 |
| int abs(a) | 返回a的绝对值 |
| double abs (b) | 返回浮点数b的绝对值 |
| floor(c) | 返回一个<=c的最大整数 |
#include<iostream>
#include<cmath>
using namespace std;
int main() {
int a = 10, b = 20, c = 3;
float d = -2.3, e = 3.4, f = 1.7;
cout << "sin(f)" << sin(f) << endl;
cout << "log(a)" << log(a) << endl;
cout << "pow(b,c)" << pow(b, c) << endl;
cout << "sqrt(a)" << sqrt(a) << endl;
cout << "hypot(3,4)" << hypot(3, 4) << endl;
cout << "abs(d)" << abs(d) << endl;
cout << "floor(e)" << floor(e) << endl;
return 0;
}
输入输出
必须的头文件 iostream 定义了 cin、cout、cerr 和 clog 对象,分别对应于标准输入流、标准输出流、非缓冲标准错误流和缓冲标准错误流。
标准输出流cout
- cout是和流插入符<< 结合使用的
- endl用于在末尾添加一个换行符
#include<iostream>
using namespace std;
int main() {
char str[] = "Hello World!";
cout << "str的值是: " << str << endl;
return 0;
}
标准输入流cin
- 与流提取运算符>> 结合使用
#include<iostream>
using namespace std;
int main() {
char str[20];
cout << "请输入你想输入的东西: " << endl;
cin >> str;
cout << "你输入的是:" << str << endl;
return 0;
}
- 流提取运算符 >> 在一个语句中可以多次使用,如果要求输入多个数据
cin >> a >> b;
//就相当于:
cin >> a;
cin >> b;
标准错误流cerr(一般用来显示错误消息)
- cerr 对象是非缓冲的,且每个流插入到 cerr 都会立即输出
- 也是与流插入运算符 << 结合使用的
char str[] = "Unable to read....";
cerr << "Error message : " << str << endl;
标准日志流clog(一般用来显示日志消息)
- clog对象是缓冲的,每个流插入到clog都会先存储在缓冲区,直到缓冲区填满或缓冲区刷新才会输出
char str[] = "Unable to read....";
clog << "Error message : " << str << endl;
C++字符串
C++提供了两种类型的字符串表示形式
- C风格的字符串
- C++引入的string类类型
C风格的(以null结尾)
char str[5]={'i','k','u','n','\0'}//是使用 null 字符 \0 终止的一维字符数组
//可以简写为:
char str[]="ikun";
C++有大量函数来操作以null结尾的字符串,使用前需加头文件 cstring
| 函数 | 功能 |
|---|---|
| strcpy(s1,s2) | 复制字符串s2到s1 |
| strcat(s1,s2) | 连接字符串s2到s1末尾 (注意s1的空间要足够大) |
| strlen(s1) | 返回字符串s1的长度 |
| strcmp(s1,s2) | s1=s2返回0;s1<s2返回值<0,s1>s2返回值>0 |
| strchr(s1,ch) | 返回一个指针,指向s1中字符ch第一次出现的位置 |
| strstr(s1,s2) | 返回一个指针,指向字符串s1中字符串s2第一次出现的位置 |
#include<iostream>
#include<cstring>
using namespace std;
int main() {
char s1[100] = "ikun";
char s2[] = "yufenfen,jiugulicaomushenikun";
char s3[10];
int len;
//复制s1到s3
strcpy_s(s3, s1);
cout << s3 << endl;
//连接s1和s2
strcat_s(s1, s2);
cout << s1 << endl;
//返回s1的长度
len=strlen(s1);//咱也不知道为什么这个不用加_s
cout << len << endl;
return 0;
}
C++ 中的 String 类
注意使用时包含头文件 string
string a = "google";
string b = "baidu";
string c;
c = a + b;//连接操作,这个在C字符串也能用
cout << c << endl;
指针篇
什么是指针?
指针就是一个变量,和其他变量一样,你在使用之前,需要对它进行声明/定义。
int *p;
double *q;
char *r;
既然指针是变量,那么它的值是什么呢?答案是另一个变量的地址。所以在定义完指针后,我们需要把另一个变量的地址赋值给指针。
#include<iostream>
#include<string>
using namespace std;
int main() {
int* p;
int a = 10;
p = &a;
cout << *p << endl;//最终输出10
return 0;
}
C++ NULL指针
我们上面说到,指针的值是另一个变量的地址,那么如果没有确切的值赋给指针,该怎么办??解决方法就是为指针赋一个NULL值,被赋予NULL值的指针就被叫做空指针。NULL指针是一个定义在标准库了值为0的常量。
int main() {
int* p = NULL;
cout << p << endl;//注意这里是p不是*p
return 0;
}
需要注意的是,大多操作系统都不允许访问地址为0的内存。但地址为0的内存有着重要意义,它表明该指针指向一个不可访问的内存位置,但如果指针是NULL,我们一般会说假定这个指针不指向任何东西
指针的运算
-
算术运算
- 因为指针的值是一个用数值表示的地址,假设我们有指针p,指向地址1000,那么p++后,指针指向1004,p--后,指针指向0996。
-
指针的比较
- 可以用关系运算符< = >来比较两个指针,前提是两个指针指向两个相关的变量,比如一个数组的两个元素。
#include<iostream>
#include<string>
using namespace std;
int main() {
int a[5] = { 1,2,3,4,5 };
int* p;
p = &a[0];
int* q;
q = &a[2];
cout << *p << endl;//1
cout << *q << endl;//3
*p++;
*q++;
cout << *p << endl;//2
cout << *q << endl;//4
for (p; p <= &a[5]; p++) {
cout << *p << endl;//如果p的地址<=a[5]的地址,就输出*p
}
return 0;
}
指针和数组
一个指向数组首地址的指针,可以通过指针的算术运算来访问数组(本质就是一个指针指向一个数组首地址)
#include<iostream>
#include<string>
using namespace std;
const int MAX = 5;
int main() {
int a[MAX] = { 1,2,3,4,5 };
int* p;
p=a;//默认p指向数组a的首地址
//也可以写成*p=a[0];
cout << *p << endl;
cout << *(p + 1) << endl;//指向a[2];
return 0;
}
指针数组 : 可以定义用来存储指针的数组。
在上面已经学会了用一个指针指向数组首地址,然后根据指针运算最终可以访问到全部数组。那么有没有一种方法,不需要指针运算,就可以使用指针访问数组捏?那就是指针数组,定义一个数组,里面存放若干指针,使用一个for循环,使每一个指针都指向一个数组内的元素,就ok了。本质是让数组内的每个指针指向需要指向的数组内的元素,并不是指针数组指向另一个数组
#include<iostream>
#include<string>
using namespace std;
const int MAX = 5;
int main() {
int i;
int a[MAX] = { 1,2,3,4,5 };
for (i = 0; i < 5; i++) {
cout << "a[" << i << "]的值是" << endl;
cout << a[i] << endl;
}
int* p[MAX];//指针数组,数组里面有5个指针
for (i = 0; i < 5; i++) {
p[i] = &a[i];//给每个指针赋予数组元素的地址
}
for (i = 0; i < 5; i++) {
cout << "p[" << i << "]的值是" << endl;
cout << *p[i] << endl;
}
return 0;
}
传递数组给函数
C++传数组给一个函数,数组类型会自动转换成指针类型,因而实际传的是地址
假设我们要传一个一维数组到函数里,共有三种方法
- 法1:形式参数是指针
void max (int *p)
- 法二:形式参数是一个已经定义大小的数组
void min (int a[10])
- 法三:形式参数是一个 未定义大小的数组
void max (int a[])
实例
#include<iostream>
#include<string>
using namespace std;
const int MAX = 5;
int sum(int* ptr) {
int i = 0, sum = 0;
for (i; i < MAX; i++) {
sum = sum + *(ptr + i);
}
cout << sum << endl;
return sum;
}
int main() {
int a[MAX] = { 1,2,3,5,4 };
int* p;
p = a;
sum(p);
return 0;
}
//法二
int sum(int a[MAX]) {
int i = 0, sum = 0;
for (i; i < MAX; i++) {
sum = sum + a[i];
}
cout << sum << endl;
return sum;
}
//法三
int sum(int a[]) {
int i = 0, sum = 0;
for (i; i < MAX; i++) {
sum = sum + a[i];
}
cout << sum << endl;
return sum;
}
从函数返回数组
C++是不允许直接返回一整个数组作为函数的返回值的,所以我们用一个指针指向这个数组,返回这个指针即可
int * sum ( ){
}
实例
注意:C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。
#include<iostream>
#include<ctime>
#include<cstdlib>
using namespace std;
const int MAX = 5;
int* getrandom() {
static int a[10];//C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。
srand((unsigned)time(NULL));
for (int i = 0; i < 10; i++) {
a[i] = rand();
cout << a[i] << endl;
}
return a;
}
int main() {
int* p;
p = getrandom();
for (int i = 0; i < 10; i++) {
cout << *(p + i) << endl;
}
return 0;
}
传递指针给函数
形参写上指针类型,直接传就行。如果指针指向的是一个数组,就相当于传一个数组给函数,即传递数组给函数的法一
int max (int *p){
从函数中返回指针
即从函数中返回数组的方法。C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static变量。
int *p max ( ){
指向指针的指针
指针的指针就是将自身的地址存到另一个指针里面。
int main() {
int a = 100;
int* q;
int** p;
q = &a;
p = &q;
cout << **p << endl;//100
return 0;
}
引用&
什么是引用
引用是给已存在的变量取了一个别名,通过引用变量名称可以指向变量
格式
//类型 &引用变量名 = 已定义的变量名
int a = 1;
int &c = a;//即c是初始化为a的变量的引用
引用和指针的区别
通过上面我们可以知道,通过引用也可以指向一个变量,这和指针的效果是相同的,那么二者有什么区别呢?
- 不存在空引用,即引用必须指向一块合法的内存;而指针可以是一个空指针
- 一旦引用被初始化指向一个对象,它就不能指向另一个对象了;而指针可以随时变化指向的对象
- 引用必须在创建时被初始化,指针可以在任何时候被初始化
实例
#include <iostream>
using namespace std;
int main (){
int i = 3;
int j = 4;
int &a = i;
int &b = j;
cout << a << endl;
cout << b << endl;
return 0;
}//输出为3 4
把引用作为函数的参数 ⭐
#include <iostream>
using namespace std;
void swap(int &a,int &b){
int t;
t = a;
a = b;
b = t;
}
int main (){
int a = 20;
int b = 10;
swap(a,b);
cout << "a=" << a << endl;
cout << "b=" << b << endl;
return 0;
}//a=10,b=20
把引用作为函数的返回值
- C++返回一个引用,方式与返回一个指针类似
- 当返回一个引用时,则返回一个指向返回值的隐式指针,这样函数就可以放在赋值语句的左边。
#include <iostream>
using namespace std;
int a[5] = {1,2,3,4,5};
int & setelem(int i){
int& b = a[i];
return b;
}
int main (){
for(int i=0;i<5;i++){
cout << a[i] << endl;
}
setelem(2) = 20;//改变第二个元素
setelem(3) = 30;//改变第三个元素
for(int i=0;i<5;i++){
cout << a[i] << endl;
}
return 0;
}