CPP基础教程

Koarz
2024-08-24 / 0 评论 / 0 阅读 / 正在检测是否收录...

基本数据类型

C++的基本数据类型有int, char, bool, short, long, long long, float, double, void, unsinged int(short, long long...), std::nullptr_t,具体可查types

这么多都要记住?nullptr_t其实不用,其他的是的,不过这里给个建议,你只需要记住int, char, bool, long long, double, float, void这几个即可如果你需要无符号数那么在他们前边加上unsigned就好了。

接下来对他们做个分类:

整数浮点数
intfloat
long longdouble
short

char和bool存储起来是按整数存的但是他们的作用并不是储存整数,所以我并没有把他们放在上边的表格里,char是字符类型可以存储ascii码表里所有的字符,bool是布尔类型用来存储true或false(在c++中我们判断条件应该用bool而不是整数01,当然他们会被转成bool类型,所以使用还是不影响的)

接下来介绍一下它们的大小,直接上图types size

这里有一个误区就是你在学习c语言课的时候可能老师跟你讲这个类型具体是多少多少字节,但是C++标准只规定了最少应该是几字节(上图显示为bit)并没有说具体就应该是几字节,所以在某些系统中你可以看到int居然是2字节?!什么这里short和int相同?long怎么在这是64位在那就32位了,这都是因为它们的大小是实现定义的,只规定了至少应该是多少位,而不是一定。(拓展:在以后开发时如果你不想因为数据大小造成困扰,那么就使用int_16, int_32, int_64类型的数据,这样就不用担心数据位宽不符合了)

接下来就是他们能表示数的范围,对有符号整数来说N位有符号整数的最小保证范围从-(2^(N-1) - 1)到(2^(N-1) - 1),浮点数因为存储格式与整数不同,所以前边的结论并不适用于浮点数,浮点数存储结构大概是符号位 偏移 数据,这里我们假设符号位是0也就是正数,偏移是10(2),数据是101(2进制),那么这个数就是 110.1(2进制)也就是6.5,数据前补1,浮点从.101向后移动两位,所以浮点数可以表示远大于它的相同位宽的整数,但是数据会丢失精度,如果你需要高精度计算,千万别用浮点数

这里还有一个误区就是char的表示范围(内部储存的整数)这也是实现定义的,有可能是-128到127也可能是0到255

输入输出

在c语言中标准输入输出就是scanfprintf,c++也支持这两个函数,不过更c++的写法是输入输出流即使用cincout,相比较来说各有优劣,这两套的区别就在格式化,看你是否需要格式化输入输出来选择合适的方法。

cin、cout的用法如下

#include <iostream> // 包含cin cout的头文件
using namespace std; // 这里是跟命名空间有关,这里先不讲
int main() {
  int a;
  cin >> a; // 这里>>是流操作符不是大于大于,代表将输入流中的数据传给a
  cout << a; // <<也是流操作符,这样你就可以看出流操作的方向,上边是cin流向a,这里是a流向cout
  return 0;
}

运行之后你输入几就会再输出几,如果你需要一次性输入更多变量(不需要格式化的,每个变量以空格或者回车结尾的),那么就可以直接这么写

#include <iostream>
using namespace std;
int main() {
  int a, b;
  char c;
  cin >> a >> b >> c; // 输入 a b c
  cout << a << b << c; // 输出 a b c
  return 0;
}

还有一点,由于cin这套会和scanf这套同步缓冲池,所以cin这套的速度比scanf这套慢一倍,想要cin达到同样的性能可以直接解绑

#include <iostream>
int main() 
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    // 上边两句就是解绑,使用之后你用cin读取的数据就不能通过printf输出了也就是不能混用了
}

数组

数组是一段相同类型的元素的集合,它们在逻辑上是连续的,在内存中也是连续的,数组的声明方式为类型 数组名[数组大小],例如我需要一个长度为10的int类型的叫arr数组,那么声明时就写int arr[10];,这个数组占的空间就是sizeof(int) * 10(这个sizeof 是关键字可以求后边类型所占字节),当我们需要访问数组中的某一个元素的时候,就需要通过下标访问,数组的下标是从0开始的,也就是说第一个元素的下标为0而不是1,第二个是1,以此类推,c/c++在你访问数组元素时并不会检查下标是否合理(在不在数组范围内),所以在操作数组时你需要严格控制操作的下标范围。

当你需要一些类型相同的元素的时候就可以使用数组,比如说我要输入10个元素,如果你不会用数组的话那你可能会写

int a, b, c, d, e...;
cin >> a >> b >> c...;

这很明显声明起来并不方便,10个元素还好如果是10000个呢?不能一个一个起名吧,这时候就可以使用数组int a[10000];搞定。不过这个时候输入输出就成了大问题,那么接下来就需要与循环结合起来,才能充分发挥数组的功能

数组可以是多维的,例如二维数组可以声明为int arr[10][10];这样就得到了长10宽10的二维数组(不过他们在内存上还是连续的等同于int arr2[100]),当你需要访问第一行第二列的元素时就使用下标arr0;三维甚至更高维的数组同理

循环选择结构

C++的循环选择与C语言并没有区别,选择会用到if else switch,循环则有for、while、do while。用法如下:

if (条件) 条件为true执行语句
else 条件为false执行语句

else并不是必须的,看你自己需求,if和else后边都只接一条语句,需要接更多语句就需要使用大括号包围起来

int a;
cin >> a;
if (a == 10)
  a = 5;
  cout << "a = 10\n";
else
  cout << "a != 10\n";

思考一下上边的语句是否正确。看出问题了吗?没错这里的if后边其实只接了a = 5;这一条,else会找不到它对应的if,所以这时候会编译错误,去掉else之后cout << "a = 10\n";这句则是一定执行的所以千万要记得if else for while do后边接多个语句时要加大括号,原因可以看作用域这部分。

所以上边代码的正确写法应该是:

int a;
cin >> a;
if (a == 10) {
  a = 5;
  cout << "a = 10\n";
} else {
  cout << "a != 10\n";
}
// 养成良好习惯,一句也加大括号

有的人可能认为else if也是关键字什么的,其实不是,else if就是else语句后接了个if语句,本质上为

if (condition) {
  blabla...
} else {
  if () {
    blabla...
  }
}

for的使用形式为for(刚进入循环时执行一次的语句; 循环执行条件,为true才可以继续执行; 一次循环结束后执行的语句),没什么好讲的,直接上代码!

int a[100];
for (int i = 0; i < 100; i++) {
  cin >> a[i];
}
for (int i = 0; i < 100; i++) {
  cout << a[i];
}

上边的代码做了什么事呢?

首先是申明了一个大小为100的数组,之后在循环开始时申明了一个变量 i 并初始化为0,再判断i < 100为true,之后开始执行循环也就是cin >> a[i],循环体内所有语句执行完毕,自动执行i++,让i = 1...

while的用法很简单,只有一个判断条件while(条件),条件为真则继续执行,使用while实现与上边for相同功能的写法是这样的:

int a[100];
{
  int i = 0;
  while (i < 100) {
    cin >> a[i];
    i++;
  }
}
{
  int i = 0;
  while (i < 100) {
    cout << a[i];
    i++;
  }
}

为什么用了大括号呢,还是跟作用域有关,总之,while只有在条件为true时才继续执行,且不能像for一样在开始执行一次第一个‘;’前的语句

dowhile和while类似,不同的是dowhile保证循环体代码至少执行一次,它是执行之后才判断条件真假,while和for则是一开始就判断

do {
  cout << "13 * 4";
} while(false); // 记得不能忘了这个分号

尽管上边的语句条件为false,但是仍然会输出“13 * 4”,这就是do while的特别之处

continue break

有的时候我们在循环体中可能想达到某些条件就提前退出或者不继续执行下边的代码,这时候就要用到continue、break控制了,continue的作用是跳过下边的代码,执行到循环尾也就是

while (true) {
  a;
  continue;
  d;
  b;
} <-这个地方
// 这是个死循环

之后开始下一次循环,break是直接退出循环

while (true) {
  a;
  break;
  d;
  b;
}
// 退出循环,不是死循环

执行a之后就不会执行db了。

那么实战中一般会怎么使用呢?,比如从0到100,输出所有奇数

int a = 0;
while (true) {
  if (a > 100) {
    break;
  }
  if (a % 1 == 0) {
    continue;
  }
  cout << a << ' ';
}

上边的例子即使用了break也使用了continue,你可以一步一步调试看看代码执行时发生了什么

switch的功能有点鸡肋,你必须穷举所有结果,一般switch都跟break一起用,这就是为什么我在这才写switch,switch后边跟条件,但是与if不同,它不是判断真假,而是将条件进行匹配,这用到了另一个关键字case

int a;
cin >> a;
switch (a) {
case 12:
case 14:
  cout << "wow!\n";
  break;
default :
  cout << "emm..\n";
}

上边的代码在输入12或者14时候会输出wow,其他情况都是emm..,这个例子同样表明不是所有case后都得跟break,如果不跟的话就会继续延顺下去,也就是说下边这种情况输入12时会输出good haha,输入14就是haha

int a;
cin >> a;
switch (a) {
case 12:
  cout << "good "
case 14:
  cout << "haha\n";
  break;
default :
  cout << "emm..\n";
}

指针

终于到了指针了,首先我要说明:指针一点也不难!

指针也只是一个变量,它的标志就是类型中带有一个 * 号,比如int*就是int类型的指针,它可以指向一块类型为int的变量的内存,记住指针保存的就是地址就行了,这里不能理解的话那么我们抽象一下

你是一个人,你叫做A,用代码表示就是人 A,你在这个地球上,你的位置(地址)是唯一的,因为不可能有人跟你重合,我们可以通过&得到你当前的位置比如经度117.42 纬度19.24 海拔345米,我们可以用一个叫人* pos = &A;来保存这个数据,同理,在程序运行时,每个变量也会有一个唯一的地址,这里不展开讨论,有兴趣的话自行了解,这个地址根据系统不同得到的结果也不同,64位系统就是64位数字32位系统就是32位数字,这个地址一定是整数,且是不会变的,我们可以通过这个地址找到这个变量的位置读取它或者修改它。

int a{}; // a = 0
int *b = &a; // b = a 的地址
*b = 5; // a = 5,通过解引用修改b指向的那块地址的数据(a)的值
int c = *b; // c = 5,通过解引用读取b指向的a的值

记住指针是一个变量,它保存的值是其他变量的地址,通过解引用就可以访问指针指向的变量。(*b)此刻就是a

还有一点是数组与指针之间的关系,很多人都会搞错,先给出结论:数组和指针是完全不同的两个东西,唯一的关系就是数组可以退化成指针。

int arr[100];
int* p = arr;
// 也就是说,这句的意思不是数组和指针一样所以可以给p赋值arr,而是arr在这里退化成了指针,arr的地址储存在了p中
// 最简单的证明方法就是你不能给一个数组赋值指针,下边这句是不允许的
int* p2;
int arr2[100] = p2;

一个完整的数组包含了长度信息,而指针是不会包含这部分信息的。不过值得一提的是你可以对指针进行中括号运算。

int arr[100];
int* p = arr;

p[n] == arr[n] // 这个式子是恒成立的

中括号运算本质上是通过指针 + 偏移计算元素位置,就是 p[n]*(p + n)是等价的,所以你可以写出这样的代码0["ABC"]得到的结果是A。

最后一小点,c++中的空指针默认为nullptr而不是NULLnullptr就是最开始提到的std::nullptr类型的数据,在写c++代码时,如果你要表示空指针,请用nullptr而不是NULL或者0

函数

函数的作用是增加代码复用,因为有的句子你可能会重复写很多次,假如有的功能要几百行几千行,你又需要多次使用该功能,总不能一遍又一遍写吧,哪怕复制粘贴也是需要改一些参数的,还是很麻烦,所以这时候就可以通过函数包装一下。

一个完整的函数需要 返回值类型 函数名 (函数参数) { 执行代码; 返回值;(返回类型为void时不需要返回值,但是可以通过return提前结束函数执行)}举个简单的例子

int add(int a, int b) {
  return a + b;
}

int main() {
  std::cout << add(3, 4); // 输出7
}

C++的函数可以通过修改参数进行重载,以下是一些例子

(1) int a() {
  ...
  return ...;
}
// 先声明函数a
(2) double a() {

}
// 错误,修改返回值类型不能重载函数
(3) int a(int x) {
  ...
}
// 没问题,这个函数a比(1)多了一个int参数
(4) double a(double x) {
  ...
}
// 没问题,(4)和(3)的参数类型不同
(5) double a(int x) {
  ...
}
// 错误,参数类型和(3)冲突
(6) int a(int x, double y) {
  ...
}
// 没问题
(7) int a(double x, int y) {
  ...
}
// 没问题,和(6)不冲突,参数虽然数量一样,但是对应参数类型不同

重载函数之后在执行函数时程序会选择最匹配的函数执行

(1)
int a(int x) {
  ...
}

int main() {
  a(3.14) // 可以执行a函数,但是3.14会被转为整型也就是 3
}
------------------------------------------------------
(2)
int a(int x) {
  ...
  return 1;
}

int a(double x) {
  ...
  return 2;
}

int main() {
  a(3.14); // 返回2
  a(3); // 返回1
}

函数的参数是复制进去的,所以如果你需要修改一些数据,是不能通过直接传参完成的

void fun(int a) {
  a = 10;
}

int main() {
  int b = 89;
  fun(b);
  /** 
  b这时候还是89,不会在fun中被修改为10
  这是因为在调用fun函数时,我们执行的操作是将b赋值给a,这时候a就是一个和b完全无关的变量
  对a执行任何操作都与b无关
  **/
}

想要修改b为10,我们有两种做法,第一种做法是通过指针来修改,如我前文所说,通过指针可以找到变量本身,这样我们修改的就是我们想要的变量,而不是一个通过复制得到的变量。

void fun(int* a) {
  *a = 10;
}

int main() {
  int b = 89;
  fun(&b);
  // ok, 现在b = 10
}

开始讲第二种方法前,我们再引入一个新的概念引用,引用说简单点就是变量的别名,小花有一个别名叫张三,有的人喜欢叫小花,有的人喜欢叫张三,虽然名字不一样,但是表示的还是同一个人,引用就是这样的,你可以通过给一个变量起别名来访问该变量。引用的声明方式是在类型后加一个&(&作用真多...)

int a;
int &b = a; // b是a的别名
int &c = a; // c、b都是a的别名
int &d; // 错误,引用在申明时就得初始化!!!
b = 10; // a,b,c都等于10

引用作为函数参数时,可以直接修改原数据

void fun(int &a) {
  a = 10;
}

int main() {
  int a;
  fun(a);
  // a = 10
}

作用域与生命周期

这一部分我觉得是非常简单的,总结起来就一句话一对大括号内的内容属于一个作用域,变量的生命周期从申明开始到离开大括号结束,全局变量、静态变量生命周期贯穿整个程序。举例子:

1.  void f() {
2.  int a;
3.  int b;
4.    {
5.      int c;
6.      std::cin >> c;
7.    }
8.  std::cin >> c;
9.  int c;
10. }

想想上边的代码有问题吗?如果有那是第几行?

这个函数中我们声明了两次变量c,错误点在这吗?当然不是。我们把1-10行叫作用域1,4-7行叫作用域2,我们先在作用域2声明了c并使用cin输入c。在离开作用域2之后又再一次尝试输入变量c,这时候就有问题了,离开了作用域2,变量c的生命周期结束,这时候在cin时不可以的,因为不知道c是啥,所以错误点在第8行。

变量ab的生命周期一直到函数执行完毕。你可以自己写一些例子实验一下,亲自体会一下生命周期、作用域。

0

评论 (0)

取消