暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

c++ 学习笔记(一)

狗头Rser 2021-11-13
322

Part1前言

准备转行跑路了,先回味回味 c++,遥感的局限还是太大了,在一个没有资源的情况下,却一定要去凑一些东西,感觉与其三年都做无谓的数据了,不如早点转开发保平安,遥感就随缘看看吧,深度学习,学了个寂寞,调包调参侠,有啥意思,后续反正周六和晚上 c++,计组、计网、操作系统。平常打工,遥感有啥新奇的事,深度学习有啥有趣的事就随缘写写呗。

这个公众号本身就随缘玩的,随缘写的,我开心就好!

在看 Primer c++ 第 5 版,本科的时候 c++ 学了跟没学一样,但是为了数据结构,为了工作,还是得好好学 c++ 呀,虽然不知道有多少遥感的朋友在看这个公众号,但是我决定还是先跑路了。想起了本科室友的哥哥,在空天院学遥感也学计算机转开发跑路去了。我只能说,这几个月下来,我只看到了“学术”上的遥感,看不到任何工业上的遥感,看不到

Part2序言

1经典 cin 和 cout

#include<iostream>
using namespace std;

int main(){
    std::cout<< "Hello World"<<std::endl;
}

经典 Hello World
,但是读这本经典的书看到什么现在回去理解总会有不同的感受

#include<iostream> 库告诉编译器 (compiler)
使用的是 iostream
库,和 \usepackage{}、import ..
差不多。iostream
头文件

iostream
库,包含两个基础类型 istream
ostream
分别表示输入流和输出流。

<<
运算符接受的是两个运算对象,左侧为 ostream
的对象,右侧为打印的值,

对于 std::cout<<"Hello World"<<std::endl;
就等价于

(std::cout<<"Enter two numbers:") << std::endl

相当于分别执行了:

std::cout<<"Hello World" ;
std::cout<<std::endl;

第二个运算符打印 endl
,被称为操作符,写入 endl
表示结束当前行

而对于 std::
来说,指出 cout
endl
是定义在 std
的命名空间(namespace)
中的,命名空间可以避免不经意的定义冲突。与 <<
 对应的就是 >>
表示的是,左侧接受一个  istream
作为左侧运算对象,接受一个对象为右侧运算对象。

2注释:

//
表示单行注释, /* */
表示多行注释,但是注释不能嵌套,

3while 语句

经典的 i++
++i
, i++
表示先引用 i
的值,i
再加 1,而 ++i
表示先进行 i = i +1
再将新的值取出。

对于输入不定量的值的时候可以将 cin
放在 while 里面,

4小综合应用呗

#include<iostream>
using namespace std;
int main(){
 int currVal = 0, val = 0;
 //如果确实有输入
 if(cin >> currVal){
 int cnt = 1;
 while(cin >> val ){
 if(val == currVal)
 cnt += 1;
 else{
  cout << currVal << " Occurs" << cnt << "times" <<endl; 
  #将输入的变量存储到 currVal 中去
  currVal = val;
  cnt = 1;
  }
         }
 cout << currVal << "occurs" <<  cnt << "times" << endl;
 }
 reutrun 0;
}

但是这个程序有个小 bug,没有经典的 keyboardinterrupt
,然后对于同一值 val
一直让 currVal = val

Part3基本的变量

对于 c++ 来说,有符号和无符号不能混用 int a= 1,unsigned b = -1
,但是 a * b = 4294967295
.

对于无符号变量来说,它永远不会小于 0 ,所以就牵涉到了 for
循环还有 while
循环里是否先进行减法的问题。

for (unsigned u  = 10;u  >= 0;u -- ){
    cout << u; 
}

由于迭代成 0 之后,然后继续执行 for 语句里面的内容,表达式 --u
从从当 u
中减去 1,得到的结果 -1 不满足符号的要求,-1
被转换成合法的无符号数,如果 int
占 32 位,则当 u
等于 0 时, --u
的结果会是 4294967295
,如果使用 while
来代替 for
循环,则会先进行一次判断,然后再转

初始化和赋值是有区别的,初始化只是创建一个变量赋予一个初始值,而赋值是把对象的当前值给擦除。

对于 c++ 来说有 extern
声明和定义
两种形式,而声明的值在函数体内部不能够初始化,例如:

int main(){
 extern  int i;
 int j = 1;
 i = 1;
}

就会报错

而如果在 main
函数之外,直接用 extern int i = 1
;程序能够正常编译运行。

变量能够被定义一次,但是可以被多次声明.

如果在多个文件中使用同一个变量,必须将声明和定义分离,变量的定义只能出现在一个文件中,而其他用到该变量的文件必须对其就行申明。却绝对不能重复定义。

静态语言

c++ 是一种静态的语言,即,在编译检查阶段,进行类型的检查,如果不支持运算,则程序会报错并且不会生成可执行程序。

而程序越复杂,静态检查就更容易发现问题,前提是编译器知道每一个实体对象的类型,所以,在使用每个对象前必须声明其类型。

对于 c++ 来说,有些规则是不被允许的。

  1. 不能连续出现两个下划线。
  2. 也不能以下划线紧跟大写字母连着。
  3. 定义在函数体外的标识符不能以下划线开头。

定义的几条规范:

  1. 标识符体现实际的含义
  2. 变量名一般用小写开头。
  3. 用户自定义的类名一般以大写开头
  4. 如果标识符由多个单词组成,单词之间应该有明显的区分, 例如 student_name

5引用

引用一般为左值引用,例如 int val = 1
, int &refVal = ival;
一般在初始化变量时,变量是被拷贝到新建的对象中。然而定义引用的时候,程序把引用和它的初始值绑定在一起,而并非直接将初始值拷贝给引用,引用将它的初始值一直绑定在一起,所以无法重新绑定到另一个对象,所以引用必须初始化。

引用并不是对象,只是为已经存在的对象起另外一个名字。

例如 int i = 1024, &r = i;
cout << r <<endl;
输出的是它的值,&r
则是输出的它的地址。而它的地址会与 i
的地址一样。由于是绑定, 所以,引用和被引用的对象的类型必须是一致的,同时,引用的必须是一个对象,而非一个常量,例如 int &ref4 = 10;
就会报错。

而一旦引用,引用和被引用的对象就被绑定在一起,例如:

int i , &ref = i;
i = 5;
ref = 10;
cout << i << " "<< ri <<endl; 

程序输出的是 10,10
就说明两者已经被绑定在一起了,即这个引用不是单独的一个对象了。

6指针

指针应该是最难的了!!当年本科就没学明白,

指针本身就是一个对象,允许对指针进行拷贝和赋值,而指针在其有效时能够指向不同的对象,指针可以不定义初始值。

但是现在回来看指针,一下就看明白了!!

int ival = 42;
int *p =  &ival;

指针指向的是对象的地址,例如:

int ival = 42;
int *p = &ival;
cout << "p 的地址是  " <<  p <<  "\n" <<  "p 的值是" <<   *p << endl;

所以还可以进行套娃,即:

int main(){
 int i = 12;
 int *p = &i;
 int *p2 = p;
 //输出的都是值
 cout <<  i <<  "\t" << *p << "\t" <<   *p2 << endl;
}

而指针的类型实际上用于它所指向的对象的类型,所以二者必须匹配。

指针的值

指针的应该属于四种状态。

  1. 指向一个对象。

  2. 指向下一个对象所占的空间的下一个位置。

  3. 空指针,意味着没有指向任何对象。

  4. 无效指针,也就是上述情况的其他值。

一旦指针指向了一个对象,就可以解引
,解引用虽然可以能够改变指针变量的值
,但是同时也修改了它指向对象的值
,实际上也是间接的修改了原来指向的变量
。。

例如

int main(){
 int i = 1;
 int *p = &i;
 // p 是一个地址,而 *p 是一个值,相当于解引用
 cout << p << *p << endl;
 
 *p = 0;
 cout << p << *p;
 cout << i
}

指针是个很 abstract 的问题,实际上,只需要记住 *
是取值的操作,而 &
是取地址的操作。在初始化指针变量的时候,实际上,*p = &i
 就是将这个指针指向了 i
的地址,而 *p
则代表了一个解引用,能够解出 i
的值。

后续就会有一堆的问题,比如,引用
取址
符号,还有指针的解引用。

例如:

int main(){
    int i = 42;
    int &ref = i;      //引用
    int *p = &i;      //指针指向
    *p = i;      //解引用
    int &ref2 = *p;   // 引用指针变量的值
}

空指针的值

int main{
 int *p = nullptr;
 int *p2 = 0;
 // 要用到  <cstdlib> 这个库
 int *p3 = NULL; 

}

书上说:新版最好用 nullptr
这个方法,尽量同时避免 NULL
的方法。

书上也同时建议:

同时初始化所有的指针。

因为访问未经初始化的指针引发后果无法预计,造成了程序崩溃,一旦崩溃,就很难再找到出错的位置。

其他指针的操作

void* 是一种特殊的指针,可以用于存放任意对象的地址。一个 *void
指针存放着一个地址,但是无法知道到底是个什么类型的对象。

利用 void*
指针能做的事比较有限;拿他和别的指针比较,作为函数的输入和输出,或者赋值给另一个void *
指针,不能直接操作 void*
所指向的对象,因为不知道所指向对象的类型。

同样指针也能够进行判断,但是无法知道是 0
值还是空指针

int main{
    int *p = 0// 空指针
    int i = 42;
    int *p1 = &i;  
    if(*p)
        cout<< "p指针合法"<<endl;
    else if(*p1){
        cout<< "p1 指针合法"<<endl;
    }
}

但是由于空指针的值是 0
,在判断是否为 True 或者 False 会报错。

复合类型的声明

一条语句能够定义出不同类型的变量,例如:

int i = 42, *p = &i, &ref = i;

在这里即创建了一个 int
的变量,也创建了一个指针,也创建了一个引用的变量。

对于定义变量的类型,有不同的方法:

//方法1
int* p1, *p2;
//方法2
int *p1;
int *p2

着两种定义 int
型指针的方法都可行,但是需要按照一种去写,否则乱则生变??

指针可以套娃:

int i = 2;
int *p = &i;
int **p1 = &p; 

cout << i<< endl;  //直接输出原值的值
cout << "一次指针 p 的地址为: " << p <<  "," << "它的值为:"  << "*p" <<endl;  //一次指针
cout << "二次指针 ** p 的地址为:"<< *p  << ","  << "它的值为" << **p <<endl;

所以在套娃的时候很容易混乱到底在用地址还是在引用值。

指向指针的引用

int i = 2,*p;
int *&r = p;

r = &i;
*r = 0;

指向指针的引用:

这玩意儿看了我半个多小时,一直不理解,看了半天发现之前理解错了,总觉得怪怪的。

其实做了这么些事儿:

  1. 创建了一个int
    型的变量和一个 int
    型的指针变量。而由于指针本身就是一个对象,能够进行引用,所以,由于要引用指针,所以 r
    也得是一个指针,不过 &r
    相当于进行了一个引用。

  2. 由于 r
    还是一个指针的变量,所以需要拿到地址,由于 r
    这个指针还需要拿到 i
    的地址,这时候 r
    也指向了 i
    ,这时候, 解引用 r
    并进行操作就会改指向的值。

例如:

int main{
 int i = 42;
 int &ref = i; // 引用 i ,这时候 引用的 ref 和 i 已经绑定在一起了,但是实际上并不存在 ref 这个变量
 int *p = ref;   // 所以这样创建指针,就相当于是错误 但是程序 仍能够运行
      // int *p = &ref;   //这样当然可以,因为 ref 这个引用已经和 i 捆绑在一起了。
 cout << "指针指向的值是 " << *p <<endl;
}

所以从左到右阅读这些定义,是最有效搞清楚这些的办法。

实际上指针变量只是在函数体内部局部修改,没法作用到指针变量。而指针的引用能够作用到原指针变量。

7常量

const int bufsize = 512;
bufsize = 1;  //这样是不被允许的,因为它已经被定义为一个常量了

初始化与 const

由于 const
变量一创建就不能改变,const
变量就一定得初始化,这与引用一致。但是 const
的值可以是任意一个复杂的表达式。但是 const
在默认的情况下,只会在文件内有效,比如,在一个文件中,bufsize
是一个 const
对象,那么文件就会找到 bufsize
然后把他们都赋值成 512
.

在多个文件中共享 const
变量

如果想在多个文件中共享同一个常量的话,需要用 extern
声明这一功能。

const
的引用

为了把引用绑定到 const
上面,可以对常量进行引用。

例如:

const int ci = 1024;
const int &ref = ci;   //可以这样引用。

下面两种方法都是错误的:

ref = 42;
int &r2 = ci;

无法直接直接修改 ref 那么就无法直接修改,同样由于 常量无法修改,那么其对应的引用也就无法修改。

常量引用是对 const 的引用:

const 的引用
简称为常量引用

const 引用和 普通的引用还不一样.

指针和常量

要想存放常量的地址,只能使用常量的指针:

int main(){
 const double pi = 3.14;
 const double *p = &pi;  //正确写法
// double *p = &pi;       //错误写法
 *p = 512;                  // 由于指向常量,无法修改
}

一个晕晕的例子

int i = 0;
int *const p1 = &j;

const int j = 1;
const int *p2 = &j;

从左到右去读代码,先找最近的符号,对于 p1
来说,最近的是 const
说明他是一个常量对象,然后接下来是 *
,说明它是一个常量指针,最后是一个 int
说明它指向的是一个 int
类型的数。 而对于 p2
,最近的是 *
说明他它是一个指针,然后是 const int
说明它指向的是一个int
类型的常量。

接下来就更晕了!

p1
是一个常量指针,但是并不意味着它不能修改其所指对象的值,例如:

那么,这个常量指针指向的值是 int
类型,当然可以通过解引用
的方法修改它的值。

而第二种,由于指向了一个常量的对象,想通过解引用
的方法去改原始的值,那么其实是不实际的。

例如,看下面的问题就已经很明显了,第一篇就写到这吧。

再说学好了 c++  和 java 还怕搞不明白 python ??


文章转载自狗头Rser,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论