# 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. 内存
- 内存是个临时的存储数据的容器。
- 当程序开始运行时,程序用到的数据就会放在内存中
- 然后将内存中数据调取到CPU中进行运算。
- 运算完成后将结果传输出去,数据再放回内存中。
指针变量:存放变量在内存中的地址。定义一个指针后,它里面存放的是随机地址,此时它是野指针,操作野指针非常危险,所以要给指针赋值。
取地址符:& 例如:
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. 堆内存
有固定作用域的变量,也称为自动变量,即内存空间的分配和销毁是自动的。大括号
{}
结束就销毁释放,便无法再使用这个变量。自动变量有的地方也叫栈内存,但还是有差别。c++中自动变量用栈的方式管理。栈是先进后出。
堆内存:与栈内存不同,堆内存上的空间不会随着作用域的结束而被释放回收。所以堆内存上的空间必须要手动释放。堆内存用到的函数在
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. *与&
*在不同地方的含义
- 在定义指针中时,*为指针定义符
- 在可执行语句指针之前,*为指针引用符
- 别忘了还有运算符的作用
&在不同地方的含义
- 在定义变量中,&为引用定义符,且必须初始化引用。
- 在可执行语句中,&为取址符
# 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函数结束时销毁再调用析构函数
← 运行跨域的chrome 1.设计哲学 →