一、基础部分
前面都是一些小菜,后面会省略掉python一些相关内容。
1.输出流
我们都知道C++中float类型7位有效数字,double类型15-16位有效数字。但对于这种小数输出时在不做任何设置的情况下,我们只会看到6位有效数字,即小数点前后有效数字加起来为6,小数点前是4位,那小数点后就是2位。
cout << 3.1415926535 << endl; // 3.14159 cout << 31.415926535 << endl; // 31.4159 cout << 314159.26535 << endl; // 3114159
若小数点前超过了6位,则会以科学计数法表示。
cout<< 3141592.6535 <<endl;// 3.14159e+06
对于这一点Python就好多了
print(3.1415926535897) # 3.1415926535897
那天一个学弟问我一道C语言的题,虽然这是道陈年老题,但还是要长个记性。
2.类型大小
我们都知道大部分的语言比如int类型都是占4个字节的,而python的int类型占24+4个字节,另外24个来自于其内部的封装,(列表数组我们后面再说)。当然,你像anaconda的一些科学计算库里面的数据类型,为了计算高效MinGW都是给你整的明明白白有大有小的。
cout << sizeof(100) << endl; //4
a = int() print(a.__sizeof__()) # 24 b = int(100) # 即 b=100 print(b.__size0f__()) # 28
3.除法
对于计算,谁闲的会去把0当作除数或取模,两种语言都会报错。
对于C++,整数除以整数还是整数,整数/浮点数除以浮点数可能是整数可能是浮点数;
cout<< 3/2 << endl; // 1 cout<< 5/2.5 << endl; // 2 cout<< 5/2.2 << endl; // 2.27273 cout<< 0.5/0.25 <<endl; // 2 cout<< 0.5/0.22 <<endl; // 2.27273 cout<<0.5/5<<endl; // 0.1
哪怕你是用自己定义的变量来接收
float a1 = 5 / 2.5; int a2 = 5 / 2.5; float d1 = 2.5 / 5; int d2 = 2.5 / 5; cout << a1 << endl; // 2 cout << a2 << endl; // 2 cout << d1 << endl; // 0.5 cout << d2 << endl; // 0
float a = 0.5f //double float
对于python,注意是python3之后的。2还是3/2=1。对于整除Python用//表示。
print(3 / 2) # 1.5 print(5 / 2.5) # 2.0 print(0.5 / 0.25) # 2.0 print(0.5 / 0.22) # 2.272727272727273
3. 字符和字符串
对于c++,字符用单引号,字符串用双引号。python中单双三引号其实都可以。我本以为这就够了,有意思的是在学习JS的ES6时我还得知反引号可也用于包裹字符串。
char a = '哈'; char name1[] = "呃呃"; // 双引号 string name2 = "你好"; // #include <string> #include "string" cout << name1<< "," <<name2 << endl;
4. 对于制表符
cout << "aaa\tbbbbb" << endl; cout << "a\tbbbbb" << endl; cout << "aaaaaa\tbbbbb" << endl; /* aaa bbbbb a bbbbb aaaaaa bbbbb */
print("aaa\tbbbbb") print("a\tbbbbb") print("aaaaaa\tbbbbb") ''' aaa bbbbb a bbbbb aaaaaa bbbbb '''
惊讶不,一个八位一个四位。
我更惊讶了,电脑端和手机端看到的不一样(来自iphone13用户)。
5. 对于输入
我们输入空格,意味该值我们已经输入完。
首先,如果你定义接收的类型和你输入的类型不符,那么它不接收。
int d; float e; cout<<d<<", "<<e<<endl; cout<<"请输入一个整型,一个浮点型:"<<endl; //输入多了会影响到下一次 cin>>d>>e; cout<<d<<", "<<e<<endl; string f; cout<<"输入一个字符串:"<<endl; cin>>f; cout<<"ok, "<<f<<endl; //有空格就不继续了 /* 0, 9.80909e-44 请输入一个整型,一个浮点型: aa bb 123 33 0, 9.80909e-44 输入一个字符串: ok, */
如果你输入的数量大于了你接收的数量,第一次还好,但多出来的部分会影响到第二次输出。
int d; float e; cout<<"请输入一个整型,一个浮点型:"<<endl; //输入多了会影响到下一次 cin>>d>>e; cout<<d<<", "<<e<<endl; string f; cout<<"输入一个字符串:"<<endl; cin>>f; cout<<"ok, "<<f<<endl; //有空格就不继续了 /* 请输入一个整型,一个浮点型: 33 5.5 aaa 33, 5.5 输入一个字符串: ok, aaa */ // 显然我第二次还没输入呢就打印出aaa了
所以没事别为难自己。
对于Python,你输入什么无所谓的,可以后续split处理也可以一开始map split接收多个值。
a = input('快点输入:') print(a) ''' 快点输入: asd qwe2.1m 3 \n xxx asd qwe2.1m 3 \n xxx '''
6. 强制类型转换
前提是可以转换,“11”可以转换成数字11,“abc”就不能转换为数字。强制类型转换可以包括类型转换进制转换。
C++是在转换的前面加(类型)
python是直接如int(元素) 至于进制转换不知道的可以去查一下,不难。
7. 关于ASSIC码
char a='a'; cout << (int) a << endl; // 97 cout<< (char)65 <<endl; // A
print(ord('a'), chr(97)) # 97 a
8. 对于布尔类型的运算
在C++语言中,真返回1,假返回0。而python的真可能给你返回1、2、3、4等本身的值。
int g1 = 0 && 3; // 0 int g2 = 2 && 9; // 1 int h1 = 0||5; // 1 int h2 = 5||0; // 1 int h3 = 0||0; // 0 int t = 0||NULL; // 0
print(2 and 4) # 4 print(3 and 0) # 0 print(8 or 2) # 8 print(0 or None) # None
有意思的小障眼法:
print('False' and '0') # 结果返回0,但这个0是字符串0,本式子为真,and两边字符串都不为空。 print(type('False' and '0')) # class str
9. 三目运算符
int a = 20, b = 30, c; c = a > b ? a : b; cout << c << endl; //30
a = 10 if 10>20 else 20 print(a) # 20
C++不能像python一样写推导式我好难受。
a = [i for i in range(5)] print(a) # [0, 1, 2, 3, 4]
交换
a = 1 b = 2 a,b=b,a print(a,b) # 2 1
10. 循环
C++不支持for-else,while-else。
python不支持switch,没有do-while。没有goto但是可以第三方库引入。
11. C++数组、Py列表
C++:数组中的每个元素都是相同的数据类型,由连续的内存位置组成。
Py:元素可以是可以任意类型,连续的内存存贮每个元素的地址。
对于C++:
// 一维数组定义的三种方式 int arr1[5]; //赋值:arr1[0]=1 arr1[0]=2 arr1[0]=3 arr1[0]=4 arr1[0]=5 int arr2[5] = {1,2,3,4,5}; int arr3[] = {1,2,3,4,5}; // 类型 数组名[长度] = {值} /* 对于第一种,不赋值直接输出结果比较随意,可能是0可能是其它数字。 对于第二种,假如定义长度大于所赋的值,范围内超出的为0. 第三种根据所赋的值自动定义长度。 此外,arr1[6],arr2[99],arr3[999]等这样超出范围的,结果随机。 */
数组名:1、它可以统计元素在内存中的长度;2、它可以数组在内存中的首地址。
int arr1[5]; //赋值:arr1[0]=1 arr1[1]=2 arr1[2]=3 arr1[3]=4 arr1[4]=5 int arr2[8] = {1,2,3,4,5}; int arr3[] = {1,2,3,4,5}; cout<< sizeof(arr1)<< " "<< sizeof(arr2)<<' '<< sizeof(arr3)<<endl; cout<<"arr2数组元素个数为: "<< sizeof(arr2)/ sizeof(int)<<endl; cout<< arr1<< " "<< arr2<<' '<< arr3<<endl; cout<< &arr1[0]<< " "<< &arr2[0]<<' '<< &arr3[0]<<endl; /* 20 32 20 arr2数组元素个数为: 8 0x61fe00 0x61fde0 0x61fdc0 0x61fe00 0x61fde0 0x61fdc0 */
对于Python:
lst = [1, 2, 3.5, 'nihao', ['呵呵'], {"xx": "oo"}] print(lst) # [1, 2, 3.5, 'nihao', ['呵呵'], {'xx': 'oo'}] # 比较自由,可以随便写,print(列表名)为该列表内所有内容。 # 顺便提一下py超过了范围与C++不同会报错
我们知道C++中输出地址时是16进制的数字,而Python中则是10进制进的。
a = [1, 2, 3.5, 'nihao', ['呵呵'], {"xx": "oo"}] print(id(1)) # 140724378797744 print(id(a), id(a[0])) # 1590331192512 140724378797744
C++数组要求元素类型统一,比如统一为int类型,占4个字节,那么它的下一个元素比如a[0]和a[1]就差4字节,又规定过长度,还是连续的空间,所以比较基础。
而python中各个类型以对象的形式封装,且列表可以存储多个元素,这导致长度无法确定。因此Python中选用一块连续的内存,内存中存贮着不同地区元素的地址。且其实python的列表也是需要规定初始长度的,这些内部C语言以及帮我们完成了。
Python垃圾回收机制_suic009的博客-CSDN博客
C++不能直接切片。
C++无法直接通过等于号像Python的列表一样进行数组的拷贝(同一块内存地址),不同需要lst.copy()、tup.deepcopy()。
12. 二维数组
python我们就不说了没什么意思,往深了研究还涉及数据池,深浅拷贝等。主要看下C++:
定义方式:
//喜欢哪种用哪种 int arr1[2][3]; int arr2[2][3] = {{1, 2, 3},{4, 5, 6}}; int arr3[2][3] = {1, 2, 3, 4, 5, 6}; int arr4[][3] = {1, 2, 3, 4, 5, 6};
double arr2[2][3] = {{1, 2, 3},{4, 5, 6}}; cout<<arr2[0][0]<<' '<<arr2[0][1]<<endl; // 结果依然为 1 2
数组名:
arr arr[0] arr[0][0] 地址都是一样的,arr[0]其实就是个一维数组。
cout<<arr3<<' '<< arr3[0] << ' '<<&arr3[0][0] <<endl; // 0x61fdc0 0x61fdc0 0x61fdc0 cout<<"元素个数为:"<< sizeof(arr3)/ sizeof(int)<<endl; // 6 cout<<"数组行数为:"<< sizeof(arr3)/ sizeof(arr3[0])<<endl; // 2 cout<<"数组列数为:"<< sizeof(arr3[0])/ sizeof(arr3[0][0])<<endl; // 3
13. 函数
在C++中,你可以这样理解:
对于return 13:我们平时定义变量赋值:int a = 13; 这次return显然没有用int限制,所以在函数声明时要告诉它返回值类型。不写return 就写void记得别接收就行,写就记得带上int、float等。
注意:不只有int float void等,这个要看你的返回值是什么。如果是个对象,那就要用类去声明。
float foo(){ float a = 1.12; return a; } int main() { cout<<foo()<<endl; return 0; } // 结果输出 1.12 // 如果把float foo 改为int foo 则输出1
注意:
用什么声明函数就用什么接收函数的返回值
函数声明和函数的返回值return后写的东西要遵循声明变量的方法。
如 我声明一个想用一个对象去接收的函数的返回值,Person p = foo(); 那这个foo应该这样声明: Person foo(){ return p}。想声明指针 int *p = foo();那就 int * foo(){return &a} ,想引用 int&a=foo(); 那就 int& foo(){return a}
值传递:
可以看到下面当形参发生改变不会影响到实参,上面的a,b是开辟了新的内存存储1和2.
(数组指针等的传递后面会将,这里先看值传递。)
void foo(int a,int b){ cout<<&a<<' '<<&b<<endl; a = 100; } int main() { int a=1,b=2; cout<<&a<<' '<<&b<<endl; foo(a,b); cout<<a<<endl; return 0; } /* 0x61fe1c 0x61fe18 0x61fdf0 0x61fdf8 1 */
在python中虽然这里看到地址一样当值依然不会变,涉及的优化知识我上面放的链接有,感兴趣的可以看一下。
def foo1(a,b): print(id(a),id(b)) a = 100 def foo2(): a = 3 b = 4 print(id(a),id(b)) foo1(a,b) print(a,b) foo2() ''' 140724378797808 140724378797840 140724378797808 140724378797840 3 4 '''
函数声明:
int foo(int a,int b);
Python不需要函数声明也可以。
def foo1(): foo2() pass # foo1() 从上到下执行,此时还没见过foo2(),报错。 def foo2(): print('ok') pass foo1() # ok
C++的分文件编写:
C++的那些库就像Python的库/模块/包一样。
所以分文件就像Python导入自己写的模块一样,里面可以写类、函数、全局变量等。
1. 创建后缀名为.h的头文件
2. 创建后缀名为.cpp的源文件
3. 在头文件中写函数声明
4. 在源文件中写函数定义
这里我用的是Cloin编写的,用VS的其实是一样的,该加的我都标出来了。
# foo.h
# foo.cpp
在main.cpp里导入刚刚写的
运行结果如下:
ps: Cloin新手用户小指南,读一读英文就懂了。
14. 指针
指针的说明定义和使用
前面说了对于一个变量比如int a =10;那么内存空间0x0000到0x0004用来存储它。实际上我们在定义它的时候并不知道它的地址是多少,所以我们给它起了个名字a。a的地址&a即0x0000。
但当我们知道它的地址或者要操作它的地址时,就可以用指针来做。
可以看到,就像变量a保存了10一样,我们的指针变量p保存了a数据的地址。
我们定义一个int * 类型的变量p用来存储&a。
要分清 p,*p,int *
注意:
int *p; 这样看:int *是个数据类型 (int *)p 定义一个指针变量p,64位操作系统p占8字节。
int *p=&a; 这样看:(int *)(p=&a); 即把&a赋值给变量p,用int *声明这是个指针变量。
cout<<sizeof(int *)<<endl; // 8 cout<<sizeof(float *)<<endl; // 8 cout<<sizeof(double *)<<endl; // 8
对于*p,把*和p分开看。p变量保存地址,*指向这个地址(即取到这个地址的值)。
*p是这个地址指向的结果,所保存的数据。
int main() { int t[2][3] = { 1,2,3,4,5,6 }; cout << t << " " << t[0] << " " << &t[0][0] << " " << &t<<endl; int* p1 = t; // 错 如果t是一维数组可以这样写 int* p2 = t[0]; // 可以 *p为1 p为它的地址 其实t[0]是一个一维数组 int* p3 = t[0][0]; // 错 int* p3 = &t[0][0]; // 可以; return 0; }
如果是数组arr,应写int *p=arr;而不是 int *p=&arr;虽然cout出来的arr和&arr是相等的,但是地址的地址显然是不对的,但可以写int *p=&arr[0]。
int main() { int a = 10; int *p; p = &a; cout<<"a的地址为:"<<&a<<endl; cout<<"p的值为:"<<p<<endl; cout<<"*p为:"<<*p<<endl; } /* a的地址为:0x61fe14 p的值为:0x61fe14 *p为:10 */
为了加深理解,我们还可以把*p看成一个整体,其实此时*p就是10,我们可以:
int c = *p; cout<<c<<endl; // 10
这一步是先根据p中的地址*去取到,此时*p为10,10作为变量赋值给了c,感觉不难吧。
知道了上面的这些,指针这样直接定义,可就能看懂了:
int *p = &a; // (int *)(p=&a);
但要注意,只有在声明时可以这样写,因为int * 与p=&a是分开的,千万别后面再次用到或者改变值的时候写 *p=&a。这是要让*p这个值改为&a即a的地址吗?
回过头来看一下,我们p与&a相等,*p与a相等,我们用a对a内存的数组进行修改是肯定能改的。我们*p和a指向的是同一块内存,因此*p的赋值修改也会影响a。此外我们最初学的a其实和*p的*那步是一样的,都是拿着地址去取值。
int a = 10; int *p = &a; *p = 99; cout<<"a为:"<<a<<" *p为:"<<*p<<endl; // a为:99 *p为:99
int a 告诉系统要4个字节的空间存a,int * 则在32位(x86)操作系统使p占4个字节,64位(x64)操作系统使p占8个字节。
int a = 10; int *p = &a; cout<<sizeof(p)<<endl; // 64位操作系统,p占8字节 cout<<sizeof(*p)<<endl; // 10是int类型占4字节
空指针
指针变量指向内存空间编号为0的指针叫空指针。0x0000。
一般用于初始化指针变量,记得空指针的内存不可访问。
int *p = NULL; cout<<"p为:"<<p<<" *p为:"<<*p<<endl; // p为:0 *p为: // 上面输出就是那样 没少写 // Process finished with exit code -1073741819 (0xC0000005) // 当然了用VS就崩了
编号0-255是系统占用的,访问会出错。
野指针
我们之前给p赋的是地址 int *p=&a。 如果我们这样写肯定会报错(int *p=0x0000除外):int *p=0x1010程序无法执行,这是在让p指向这个十六进制数字,假如我们通过强制类型转换:int *p=(int *)0x1010
int *p = (int *)0x1100; cout<<"p为:"<<p<<" *p为:"<<*p<<"输出结束"<<endl; // p为:0 *p为: //p为:0x1100 *p为: //Process finished with exit code -1073741819 (0xC0000005)
这块乱写的地址没有去申请还去访问它肯定是不行的。
const修饰指针
- const修饰指针
- const修饰常量
- const即修饰指针又修饰常量
其实看const修饰谁谁就不变就行了,理解上面p,*p,int *很好理解什么是什么。
const修饰指针 指针指向的值不可以改但指针的指向可以改。
int a=10; int b = 20; const int *p = &a; // 此时*p=99不行 但p=&b可以 再强调一下别写成*p=&b
const修饰常量 指针指向的值可以改但指针的指向不可以改。
int a=10; int b = 20; int * const p = &a; // int const *p = &a; 也不报错 但最好还是int *一起。。 // 此时p不能变,即指针的指向不能变,p=&b错,*p=99可以。
const即修饰指针又修饰常量
那就都不能变呗。
指针和数组
如果把上面的知识看懂的话这里就很好理解了
int arr[5]={1,2,3,4,5}; int *p=arr; // int *p=&arr[0]一样的 但 int *p=&arr是错的 cout<<"第一个数据为:"<<*p<<endl; // 第一个数据为:1
我们遍历一下:
for(int i=0;i<5;i++){ cout<<"第"<<i+1<<"个数据为:"<<*p<<endl; p+=1; // 往后移四个字节 } /* 第1个数据为:1 第2个数据为:2 第3个数据为:3 第4个数据为:4 第5个数据为:5 */
指针与函数
我们上面知道通过值传递,形参的改变无法影响到实参,因为其开辟了属于自己的空间。
void swap(int a,int b ){ int tem = a; a = b; b = tem; cout<<a<<' '<<b<<endl; // 2 1 } int main() { int a = 1; int b =2; swap(a,b); cout<<a<<' '<<b<<endl; // 1 2 }
但若形参实参共用同一块地址,因为变量的指向相同,所以形参的改变必会引起实参的变化:
void swap(int *a,int *b ){ int tem = *a; *a = *b; *b = tem; cout<<*a<<' '<<*b<<endl; // 2 1 } int main() { int a = 1; int b =2; swap(&a,&b); cout<<a<<' '<<b<<endl; // 2 1 }
注:传递地址的时候这一步相当于:int *a=&a; 注 前后两个a不是一个东西,前形参后实参,两块空间。形参a里存着实参a的地址。*a指向这个值。
int *foo(){ int a = 1; // int arr[5]; return &a; // return arr; }
这样是正确的但没必要,栈区被清理,main中只能调用一次,后面学的引用也是,不建议:
(栈区、引用 在后面有写,不急)
指针函数与数组
void details(int arr[]) { // *arr cout<<arr<<" "<<arr[0]<<endl; // 0x61fe00 1 } int main() { int arr[5]={1,2,3,4,5}; cout<<arr<<" "<<arr[0]<<endl; // 0x61fe00 1 details(arr); // 记得这里不要写&arr 以后数组就写名,值就写&变量。 }
形参接收函数时,要么写arr[] 要么写*arr,这俩一样的。写arr会报错,普通变量怎么能接收地址呢。
因此在C++中,int、float等类型默认值传递,想要改变就把地址传进去用指针接收。数组类型你传递的时候不得不用指针接收,就是一定会改变原内容。
在python中
1.函数传参过程中,对于一些基本数据类型,如int(整型),float(浮点型),str(字符串)等,是值传递,函数内部对以上数据类型的数据进行修改时并不会改变原值。
2.对于list(列表)、dict(字典)、tuple(元组)则是地址传递,函数内部对以上数据类型操作时会改变原数据值。
小案例
用C++写一个功能函数,实现冒泡排序,要求数组在原地址上发生改变,而不是通过接收返回值来改变。
#include <iostream> using namespace std; void swap(int *a,int *b ){ int tem = *a; *a = *b; *b = tem; } void bulleSort(int *arr,int length ){ // arr[] int flage; for(int i=0;i<length-1;i++){ flage = true; for(int j=0;j<length-i-1;j++){ if(arr[j]>arr[j+1]){ swap(&arr[j],&arr[j+1]); flage = false; } } if(flage){ return; // 冒泡优化,没发生换位那就终止。 } } } int main() { int arr[10] = {1,5,3,6,6,8,3,2,9,2}; int length = sizeof(arr)/sizeof(int); bulleSort(arr,length); // &arr[0] for(int i=0;i<length;i++){ cout<<arr[i]<<endl; } }