# 1. printf, argc, argv

printf,print format,格式化打印

argc,arguments count,参数个数

argv,arguments vector,参数序列或指针

# 2. 整型

整型又可以分为有符号和无符号两个大类,这里的符号,指的其实就是正负号,有符号的数据类型,可以用来存放正数和负数,而无符号的数据类型,只能用来存放正数。

数据类型 数据长度(字节)
char 1
short 2
int 4
long 8
long long 8
float 4
double 8

float和double是浮点型

有符号和无符号的数据容量其实是相同的,拿 char 和 unsigned char 来说,他们的容量都是 2 的 8 次方,也就是 256 个数。只不过 char 类型的范围是 [-128, 127],而 unsigned char 类型的范围是 [0, 255]。

short可以当成short int简写,long可以当成long int简写

# 3. 内存

  • 内存是个临时的存储数据的容器。
  1. 当程序开始运行时,程序用到的数据就会放在内存中
  2. 然后将内存中数据调取到CPU中进行运算。
  3. 运算完成后将结果传输出去,数据再放回内存中。

指针变量:存放变量在内存中的地址。定义一个指针后,它里面存放的是随机地址,此时它是野指针,操作野指针非常危险,所以要给指针赋值。

取地址符:& 例如:

int a=10;
int *p;  //这个写法就属于野指针,正确做法是将指针赋值为空int*p=nullptr,空指针意思是这个指针不指向任何地方。
p=&a;   //此时指针变量p里存放的就是变量a的地址
// int *p = &a; // 等价于 *p = 10;

指针运算符:*(用来取得某个地址上的数据)

*pA=3,对一个字母取值,通过指针来指向变量a

nullptr 是c++为描述空指针而提供的值,与NULL区别在于nullptr只能赋值给指针变量,NULL则可以赋值给整形等其他变量。两者相较而言NULL可能会造成一些潜在的问题

int *p = nullptr;  //只能给指针使用
int a = nullptr // 	是错误的

# 4. 堆内存

  1. 有固定作用域的变量,也称为自动变量,即内存空间的分配和销毁是自动的。大括号{} 结束就销毁释放,便无法再使用这个变量。

  2. 自动变量有的地方也叫栈内存,但还是有差别。c++中自动变量用栈的方式管理。栈是先进后出。

  3. 堆内存:与栈内存不同,堆内存上的空间不会随着作用域的结束而被释放回收。所以堆内存上的空间必须要手动释放。堆内存用到的函数在stdlib.h这个头文件中。其中malloc是如何分配一片内存,返回值是指针,是分配的内存空间的首地址。释放时使用:free(指针名)

#include <stdlib.h>
#include <stdio.h>

int main(int argc, char** argv) {
 int* p;
 {
    p = (int *)malloc(4); // 分配
 }
 *p = 4;
 printf("*p: %d\n", *p)

 free(p); // 释放
 return 0;
}

# 5. 数组中元素的地址

printf("array[0]: %p\n", &array[0]); // %p 用来打印数组的地址

数组中元素的地址都是等差的,所以只要拿到第一个元素的地址,再加上相应元素的偏差,就可以拿到第二个元素的地址了。

对于数组来说,第一个元素的地址是数组名。

数组名是一个指向数组首元素的指针,是一个不可修改的常量。

#include <stdio.h>

int main(int argc,char **argv)
{
    int array[5];
    *(array + 2) = 1;

    return 0;
}

代码中的 *(array + 2) = 1; 就等价于 array[2] = 1;

注: malloc 分配内存

#include <stdio.h>

int main(int argc,char **argv)
{
    int * p = (int *)malloc(5 * sizeof(int));

    free(p);
    return 0;
}

p[2]*(p + 2) 在这里是等价的。

# 6. 值传递和址传递

值传递和址传递,都是在函数调用的时候,对参数进行了拷贝的操作,不过,址传递通过拷贝一个指针来进行跨函数作用域的的调用。

函数本身不会区分值传递和址传递,碰到int就分配int所需的空间,并把int的值进行拷贝,碰到int*也同理,值传递和址传递是为了让我们更容易理解,而做出来的抽象概念,本身并不存在。

// 值传递
void change(int a) {
 a = a - 1;
} 

int main(int argc, char ** argv) {
  int a = 5;
  change(a)
}

以上是值传递的概念,单纯地将a的值进行调用,change函数里的a变成了4,但是main函数里面的还是5,因为change函数执行完毕就销毁了里面的变量,所以其实change和main里的a完全是两个东西。

// 址传递

void change(int *a) {
 *a = *a - 1;
} 

int main(int argc, char ** argv) {
  int a = 5;
  change(&a)
}

这里进行了址传递,因为函数本质是将a的地址传递到了change函数里,所以改变的是a地址里的数据,这样主函数和调用函数的内容就一致了。

# 7. 内联函数

函数在工作的以后,C++ 会为函数分配相应的内存,而且还存在参数的拷贝。这些就导致函数在调用的时候会带来额外的内存消耗。

C++ 为我们提供了一种函数形式,叫做内联函数。在程序编译的时候,编译器会把内联函数的代码复制出来,粘贴到调用的地方。例如如下代码:

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


int main(int argc,char **argv)
{
    int a = 5;
    int b = 10;
    int c = add(a + b);

    return 0;
}

如果我们把 int add(int a, int b) 函数编程内联函数,那么这段程序在编译的时候,将会被自动处理成这样:

int main(int argc,char **argv)
{
    int a = 5;
    int b = 10;
    int c = a + b;
    return 0;
}

如何把一个函数变成内联函数呢?只需要在函数前面加 inline 关键字就可以了

# 8. 结构体

struct Student
{
   int math;
   int english;
};

int main(int argc,char **argv)
{
   struct Student stu;
   return 0;
}

# 9. g++

键入 'g++ hello.cpp ',输入回车,编译代码。如果代码中没有错误,命令提示符会跳到下一行,并生成 a.out 可执行文件。

现在,键入 ' ./a.out' 来运行程序。

可以看到屏幕上显示 ' Hello World

# 10. 浮点数存放结构

0.082 = 8.2*10^-2

10是基数

1位符号位(+),8位指数部分(-2),23位尾数部分(8.2)

# 11. 原码、补码、反码

原码:将一个整数,转换成二进制,就是其原码。

  • 如单字节的5的原码为:0000 0101;-5的原码为1000 0101。

反码:正数的反码就是其原码;负数的反码是将原码中,除符号位以外,每一位取反。

  • 如单字节的5的反码为:0000 0101;-5的反码为1111 1010。

补码:正数的补码就是其原码;负数的反码+1就是补码。

  • 如单字节的5的补码为:0000 0101;-5的原码为1111 1011。

在计算机中,正数是直接用原码表示的,如单字节5,在计算机中就表示为:0000 0101。

负数用补码表示,如单字节-5,在计算机中表示为1111 1011。

这儿就有一个问题,为什么在计算机中,负数用补码表示呢?为什么不直接用原码表示?如单字节-5:1000 0101。

可以将减法变为加法,省去了减法器。

在计算机中,我们可以看到,对其求补,得到的结果是其数值对应的负数。同样,负数也是如此。

运算中,减去一个数,等于加上它的相反数,这个小学就学过了。既然其补码就是其相反数,我们加上其补码不就可以了。

如:A - 127,也就相当于:A + (-127),又因为负数是以补码的形式保存的,也就是负数的真值是补码,既然这样,当我们要减一个数时,直接把其补码拿过来,加一下,就OK了,我们也可以放心地跟减法说拜拜了

# 11.1. 补码的补码是原码

# 11.2. 符号位也可以参与运算

用补码表示负数,另一个好处是,符号位也可以参与运算

1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]补 + [1111 1111]补 = [0000 0000]补=[0000 0000]原

# 12. 字节序

计算机硬件有两种储存数据的方式:大端字节序(big endian)和小端字节序(little endian)。

我们现在有一个整数514:。用16进制表示是0x0202,然后我们吧这个整数拆分成两个字节,第一个字节为0000 0010,第二个字节为0000 0010。

现在大部分的机器,都采用了小端字节序。但是在IO方面,则大部分使用大端字节序。例如:你要使用网络发送一个int类型的变量,要先把int转换成大端字节序,然后通过网络发送。

大端字节序又被称之为网络字节序。

# 13. 静态变量

static int a

定义静态变量

我们在函数中声明一个静态变量,它的作用域是函数内,但他不会随着函数结束而销毁,他会一直存在到程序结束。

# 14. 指针变量

指针变量其实和普通变量没有什么区别,一个函数也是可以正常返回一个指针的。

char * func()
{
   char * p = nullptr;
   return p;
}

int main(int argc,char **argv)
{
   return 0;
}

但是我们需要思考的是,什么情况下我们要返回一个指针,返回指针的时候需要我们注意些什么?

通常情况下,我们是希望为函数外提供一片内存,例如,我们可以给函数外面提供一个数组。

int * func()
{
   int arr[] = {1, 2, 3, 4};
   return arr;
}

但是这样写得话,程序会崩溃掉。原因是,arr 数组是一个局部变量,在 func 结束之后,其内存就被销毁掉了。此时在函数外面对其进行操作,自然会出问题。所以,要完成这类操作,我们需要把内存分配到堆内存上面。

int * func()
{
   int * arr = (int *)malloc(4 * sizeof(int));
   return arr;
}

这样就没有问题了,当然,既然是分配在了堆内存上,就要记得手动销毁。

int main(int argc,char **argv)
{
   int * p = func();
   free(p);
   return 0;
}

# 15. *与&

*在不同地方的含义

  1. 在定义指针中时,*为指针定义符
  2. 在可执行语句指针之前,*为指针引用符
  3. 别忘了还有运算符的作用

&在不同地方的含义

  1. 在定义变量中,&为引用定义符,且必须初始化引用。
  2. 在可执行语句中,&为取址符

# 16. 指针的本质(*:解引用、&:取地址)

int arr[10];
int *p = arr;
int *p = &arr[0];

arr里面放的是数组内第一个元素的地址,等同于&arr[0]

struct S {
int a = 10;
float b = 20;
}

struct S s;
s.a = 12;
s.b = 22;

struct S *p = &s;
p -> a = 12;
p -> b = 22;

普通变量用点,指针用箭头

# 17. new创建对象

对象分配到堆上,new 用来分配对象,delete 用来删除对象。new 会返回一个指针,在使用完毕后,要通过 delete 把这个指针指向的地址释放掉。

  Staff * st1 = new Staff();

  // 记得释放
  delete st1;

栈上实例化:

  Staff st1;

# 18. 析构函数

析构函数是用“~”定义的,若不定义自动生成,若手动定义则不再生成,可用于善后工作

实例化的时候会调用构造函数,main函数结束时销毁再调用析构函数