2022的愿望:反抗C字辈的打压。(1)

简介: 2022的愿望:反抗C字辈的打压。(1)

一、基础部分

前面都是一些小菜,后面会省略掉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;
    }
}
相关文章
|
存储 编译器 程序员
2022的愿望:反抗C字辈的打压。(2)
2022的愿望:反抗C字辈的打压。(2)
2022的愿望:反抗C字辈的打压。(2)
|
存储 JavaScript 前端开发
2022的愿望:反抗C字辈的打压。(3)
2022的愿望:反抗C字辈的打压。(3)
2022的愿望:反抗C字辈的打压。(3)
别做被大公司毁掉的年轻人
大公司和小公司各有各的问题,无论你在大公司还是小公司,都要懂得如何取舍,如何坚持,如何做自己。今天先来谈谈大公司,看看如何避免成为被大公司毁掉的年轻人。
3072 0
|
算法 Java
当前处境的思考
能愿意花时间停下来思考,需要感谢007。没有像007这样的外力促使自己写作,自己就一直处于拖延的状态,拖延的原因也许在于自己还没想清楚,自己在做什么?又准备做什么。
IT人士如何扛起工作生活两座大山
当我们年轻时,初入职场,意气风发,恨不能倾尽所有精力工作,奋发图强、建功立业。当我们有了家庭,发现我亦凡人,事业家庭想两手抓,却两难兼顾。后来,我们发现工作生活两顾本身就是一个幻想,我们做的,只有尽量使之平衡。
2284 0
|
Java C++
做决定,怎能让“自己”缺席
【来信】   贺老师,您好,我在一次有目的的搜索中发现了您给大二软件工程学生解答的问题。仔细阅读后,决定写信给您。
1391 0