C++程序设计
程序基本概念
- 标识符(变量/函数名?)
标识符由字母、数字、下划线“_”组成。不能把C++关键字作为标识符。标识符长度限制32字符。标识符对大小写敏感。首字符只能是字母或下划线,不能是数字。 - 常量
在定义的时候加上const, 比如const int就是整形常量。常量定义之后不可改变,
1常量用易于理解的清楚的名称替代了含义不明确的数字或字符串,使程序更易于阅读。
2常量使程序更易于修改。例如,在C#程序中有一个SalesTax常量,该常量的值为6%。如果以后销售税率发生变化,把新值赋给这个常量,就可以修改所有的税款计算结果,而不必查找整个程序,修改税率为0.06的每个项。
3常量更容易避免程序出现错误。如果把一个值赋给程序中的一个常量,而该常量已经有一个值,编译器就回报告错误。
————————————————
版权声明:本文为CSDN博主「算盘」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/codinglab/article/details/16970249
对于指针而言,如果const 在*前面,就表示指针指向的量是常量,不能通过指针来改变
int a;
const int*p = a;
*p = 5;
是不合法的
如果在*后面,就表示这个指针本身是常量,指向的地址不能改变
int a,b;
int *const p=a;
p=&b;
是不合法的
其实也很容易理解,const int * 意思是定义一个指向const int 的指针,所以不可以通过指针修改
int * const 意思是定义一个指向int的指针,这个指针本身是const 的。
所以我就会const int * const
3. 头文件
就是#include 的那个,带#号的表示那行是预处理指令,在编译之前执行,比如在编译之前会把你所有的#define都替换完成。
而#include 预处理就是把你要包含的头文件先预处理进你的代码里面
可以使用g++ -E main.cpp -o main.i 来看预处理完的main.i,就是你的代码应该长的样子,然后编译器才会把main.i真正编译成汇编代码。
#include <>这样的话库就必须是系统自带的,如果是自己写的头文件需要用#include ""
4. 命名空间
std就是一个标准的命名空间(standard)。命名空间就是什么呢,就是一个标记而已。
比如说变量“中山路”在广州有三个,一个在荔湾区,一个在天河区,一个在增城区。如果都用变量“中山路”来表示,那么就会造成混乱。这个时候命名空间就出来了,荔湾区::中山路就表示在命名空间荔湾区里面的变量中山路,这样就不会和天河区::中山路搞混了。
同样的,命名空间里面不仅能定义变量,也可以定义函数,比如std::sort就是在标准命名空间std里面的sort函数。
至于怎么定义命名空间
namespace my_namespace
{
int a;
}
using namespace my_namespace;
注意用using namespace的话两个namespace之间不能有同名函数、变量(重载除外)
5. 编辑、编译、解释、调试
编辑……编辑文本……编辑代码,好好理解一下
编译:把整个代码编译成机器语言,扔给机器执行。
解释:(比如python)一遍扫代码一遍把代码每一行解释给电脑听,让电脑一行行执行,所以python是有终端的,可以让你一行行输入一边输入一遍执行而c++没有。
调试:让程序在某个地方停下,可以观察内部的变量情况。c++推荐使用gdb调试。
基本数据类型
整数型:32位int,64位long long
无符号整数型:32位unsigned,64位long long unsigned
实数型/浮点型:32位float,64位double,128位long double
字符型:8位char
布尔型:1位bool(但是好像也要1字节?取决于编译器)
32位int其中一位是符号位,真正储存数据的只有31位,所以最大可以表示0b01111111111111111111111111111111=2147483647,最小可以表示0b10000000000000000000000000000000(补码)=-2147483648
long long 同理,32位unsigned就是那个符号位也用来存储数据,所以最大就扩大了一倍,最小就是0
8位char其实可以理解为一个缩水的int,有些情况下为了节省内存可以当int用但是编译器可能会警告。
(为什么不讲浮点数?怕误人子弟呗)
程序基本语句
首先定义,变量类型然后后面就可以跟变量名了
int a,b;
表示定义了一个int类型的变量a,一个int类型的变量b。
cin可以用来输入
cin >> a;
这样就可以把输入流的数据插入给a。同样scanf也能输入
scanf("%d",&a);
也可以输入。
输入输出这里不再赘述,具体去看入门教程
然后赋值语句,
a = 5;
表示给变量a赋予一个值5。同样赋予这个值也可以是另一个变量。
a = b;
表示给变量a赋予变量b的值。=成为赋值符号应该是优先级最低的运算符,运算符返回的是其右边的值,而且是从右边开始执行的。
a = b;
这个赋值语句是有值的!值就是b
a = b = c;
这样也是可以的,相当于给b赋值c,然后再给a赋值b这样其实a,b都会变成c的值。
分支语句
if(){}else{}当小括号内的值为真的时候才执行花括号里面的语句,否则执行else后面的花括号里面的语句。else及其后语句可省略,则值位假时跳过该分支语句。else会自动与最近的一个if匹配(如果这个if在其他花括号里就不会)
switch(a)
{
case 1:
break;
default:
break;
}
用小括号里面的值去匹配花括号里case后的值,如果匹配到了就从该行开始执行。如果都没有匹配到就执行default语句及其后语句。
for(;;)
{}
小括号里通过分号分成了三个语句。执行for的开始先执行第一个语句,然后执行第二个判断语句,如果语句为真才会执行循环内容(就是花括号里的东西)。执行完之后(或者遇到continue)就会执行第三个语句,执行完之后再判断第二个语句……如此反复
while(){}
小括号里面为真就执行花括号的内容,知道小括号里面为假或者break跳出
do{}
while();
先执行一次花括号里面的内容,再判断小括号的条件。如果为真就再执行一次花括号的内容,知道小括号条件为假
以上提到的多种不同用语句都可以嵌套使用。
基础运算
加减乘整除
......
不讲
除
整数只有整除,想要更精确得到小数结果就要转换为小数类型再运算
res = (float)a / b;
除数和被除数任意一个为浮点数都可以。
关系运算
>
<
==
>=
<=
!=
逻辑运算
&&与,a&&b在a和b都为真时为真,否则为假。这里计算机很懒,只要a为假就不会检测b是否为真。如果func1()&&func2(),func1()返回已经为假,那么就不会执行func2()运用这个性质可以简化代码。
||或,a||b在a或b随便一个为真时为真,否则为假。同理a为真就不会检测b是否为真。
!非,逻辑变量取反,真变为假,假变为真。!a
变量自增和自减
++和--,只能放在变量前/后
放在变量前表示执行该语句前给变量+/-1,放在变量后表示执行该语句后给变量+/-1
int a = 5;
cout << ++a;
cout << a++;
cout << a;
首先给a+1,然后输出a。
首先输出a然后再给a+1
三目运算
?:
a? b:c;
这是一整个式子,当a为真时整个式子的值为b,否则为c
位运算
按位与&,a&b表示二进制下每一位都执行与运算
比如2&3,就是0b10&0b11=0b10=2
按位或|,二进制下每一位都执行或运算
2|3=0b10|0b11=0b11=3
按位异或^,二进制下每一位都执行异或运算,也就是不进位的加法。
2^3=0b10|0b11=0b01=1
按位非~,二进制下每一位都取反
2=0b0010=~0b1101=-3(因为有符号位)
左移<<,二进制下整体向左移动,舍弃最高几位。
2<<3=0b10<<3=0b10000=16
右移>>,二进制下整体向右移动,舍弃末几位
数据库常用函数
#include <cmath>
然后去百度
至于四舍五入函数……round()
或者floor(a+0.5)
结构化程序设计
- 顺序结构
从上到下按顺序执行 - 分支结构
如果满足条件才执行代码块,否则跳过代码块 - 循环结构
只要满足条件就一直执行代码块。否则跳过代码块
自顶向下逐步求精的模块化程序设计
模块化程序设计其实有点类似于分治的思想
百度百科:在设计较复杂的程序时,一般采用自顶向下的方法,将问题划分为几个部分,各个部分再进行细化,直到分解为较好解决问题为止。
模块化设计,简单地说就是程序的编写不是一开始就逐条录入计算机语句和指令,而是首先用主程序、子程序、子过程等框架把软件的主要结构和流程描述出来,并定义和调试好各个框架之间的输入、输出链接关系
逐步求精的结果是得到一系列以功能块为单位的算法描述。以功能块为单位进行程序设计,实现其求解算法的方法称为模块化。模块化的目的是为了降低程序复杂度,使程序设计、调试和维护等操作简单化。
利用函数,不仅可以实现程序的模块化,使得程序设计更加简单和直观,从而提高了程序的易读性和可维护性,而且还可以把程序中经常用到的一些计算或操作编写成通用函数,以供随时调用。
比如说sort其实就是一个通用的函数,可以随时调用。
模块化设计又好比什么呢,我们写高精度乘法,那么划分开就是要高精度乘一位低精度,然后高精度加法。重复执行上述步骤。
那么我们就可以先定义模块实现高精度加法。
然后再写高精度乘法。
那么我怎么知道要先写什么东西呢?
这个时候流程图就出来了
流程图
使用图形来表示程序执行顺序
为便于识别,绘制流程图的习惯做法是:
圆角矩形表示“开始”与“结束”;
矩形表示行动方案、普通工作环节用;
菱形表示问题判断或判定(审核/审批/评审)环节(分支结构);
平行四边形表示输入输出;
箭头代表工作流方向。
如果箭头指回去,毫无疑问就是循环结构。
同样的还有PAD图
问题分析图(PAD)
百度:
它是一种由左往右展开的二维树型结构.PAD图的控制流程为自上而下,从左到右地执行。
- 结构清晰,层次分明,图形标准化,而且易读
- 强制设计人员使用SP方法,因而提高了产品质量
- 支持逐步求精的设计思想
- 容易将PAD图转换为高级语言源程序
- 通过机械的“走树”可以从PAD直接产生程序,该过程便于用计算机自动实现
比如
数组
数组(Array)是有序的元素序列。若将有限个类型相同的变量的集合命名,那么这个名称为数组名。组成数组的各个变量称为数组的分量,也称为数组的元素,有时也称为下标变量。
用于区分数组的各个元素的数字编号称为下标。
(真的看百度吧很详细的看到3.3停下就可以了)
要知道的就是数组里面的元素由于一次性申请所以地址是连续的(除非你溢出)
比如说int a[10];那么这10个元素的地址都是连续的,&a[0]=0x123456, &a[1]=0x12345A, &a[2]=0x12345E……以此类推,所以你只要知道一个数组第一个元素的地址,就可以通过指针加减来顺序遍历这个数组
而其实数组名a就是这个数组第一个元素的地址,不信你输出试试。
如果你不想要这个数组了,又觉得它特别占内存,那么可以用delete[]删掉。注意中括号里面没有任何东西。
如果你想让a指向一个新的空间,那么可以用new[]来重新申请一个,注意中括号里要写常量表达式。
字符串
就是一堆字符放在一个数组里面。
可以用char*来表示。一般程序识别内存里面的字符串是从数组开头地址开始向后读,一直读到有一个char的值为0就结束。
如果你有char a[6]="lover!".直接cout可以得到正常字符串,如果a[3]=0;那么只能输出lov,后面的什么都没有了。
具体可以参考这里
函数
就是……把一堆代码打包起来。
一般有以下三个属性:
- 函数名
函数的名字…… - 参数列表
可有可无,有参数的函数叫做有参函数,没有参数的函数叫做无参函数。默认参数算是参数。
声明的参数叫做形参,传入的参数叫做实参。
void fun(int x){ cout << x << endl; }
fun(1);
这里声明了执行函数fun需要一个整形参数x,这个就是形式上的参数,只要有x我就可以执行,所以这个x在函数内可以使用,只是告诉你这个参数叫做x,跟参数的具体取值没有关系。
然后下一行调用的时候,就给fun传入了一个1作为实际参数,即告诉fun我给你的x是1,叫做实参。
- 返回值
包括了返回值和返回类型,没有返回值的时候返回类型为void
int plus_mod(int x,int y,int mod)
{
x+=y-mod;
x+=(x>>31)&mod;
return x;
}
cout << plus_mod(1,2,3) << endl;
这段代码的意思就是,定义一个函数名为plus_mod的函数,需要三个形参int x,int y,int mod,返回类型为int类型。
然后大括号里面就是函数的具体实现,最后返回了x作为返回值。
最后一行调用,把实参1,2,3传入plus_mod函数中,执行,获得返回值并输出。
对于函数有没有参数,我们可以把他分成有参函数和无参函数。对于大括号里什么东西都没有的函数我们称之为空函数,比如
void pass(){}
就是一个空函数
我也不知道这样分有什么鬼用
传值参数和传引用参数
上面讲过了,参数声明的时候都是形式上的,唯有给他传入值的时候才是实际上的。
那么给他传入这个值,其实就是一个赋值的过程
x=1;
y=2;
mod=3;
x+=y-mod;
x+=(x>>31)&mod;
return x;
就是函数plus_mod的具体实现过程。
不难看出,这里是把实际参数的值复制了一份给形式参数,然后让形参带着这个值去执行代码。
所以不管形参怎么变,只要他是传值参数函数,就不会改变实际参数的值
void swap(int a,int b)
{
a^=b^=a^=b;
}
int x=3,y=4;
swap(x,y);
这样只是形式参数a,b的值变了,然而你传入的实际参数x,y的值并没有改变。
那么怎么改变传入参数的值呢?传入引用参数!
这样需要在函数定义的时候声明。
void swap(int& a,int& b)
{
a^=b^=a^=b;
}
这样传入表示我的参数a,b需要引用传入,而不是复制一份。
相当于我可以直接改变x,y的值,只不过给x,y重命名而已。
我在函数体内直接操作x,y,而不是操作他们复制出来的副本。
(听不懂?自己写代码看看不就知道了)
int x=3,y=4;
void swap1(int a,int b){ a^=b^=a^=b; }
void swap2(int& a,int& b){ a^=b^=a^=b; }
cout << x << ' ' << y << endl;
swap1(x,y);
cout << x << ' ' << y << endl;
swap2(x,y);
cout << x << ' ' << y << endl;
emmm...具体代码执行顺序自己调整……
常量和变量及作用范围
常量,顾名思义就是不变的量,在c/c++中用const修饰符来声明。如果你对const进行修改,编译器就会告诉你非法。
但是在python里面是没有这个机制的,一切靠程序员自觉
对于函数而言,常量就是不能动的变量。如果传入的是常量参数,那么整个函数体都不能修改他,但是可以读取他的值。
int plus_mod(int x,int y,const int mod)
{
x+=y-mod;
x+=(x>>31)&mod;
return x;
}
这里由于模数是不需要改变的,所以用常量传入会相对安全(虽然我也不知道安全在哪反正就是很有安全感)
传入的参数本身不一定是常量,但是在函数体内认为是常量所以不能修改。
至于作用范围……传入的参数的作用范围就是本函数,正所谓出了这家门,不是这家人。在函数体外你是不能调用函数内定义的一切东西。
递归
我真的不明白为什么递归这么难
- 初始状态
递归开始时候的状态 - 状态转移
递归其他状态来求解当前状态 - 递归边界
递归结束的条件
int f(int n)
{
if(n<=2) return 1;
return f(n-1)+f(n-2);
}
明显的求解斐波那契
我承认这个算法很烂
初始状态:求解第n项
状态转移:通过第\(n-1\)项和第\(n-2\)项来求解第\(n\)项
递归边界:第\(1\)或第\(2\)项的值为\(1\)
现在给出递归的百度定义:
程序调用自身的编程技巧称为递归( recursion)。递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
结构体
说白了就是把一堆变量打包的数据类型
举个例子,如果我要表示一个学生,首先一个学生肯定是变量,就像一个整型变量是变量。
但是学生是一个数据类型,就像整型是数据类型。
那么要表示一个整型变量,只需要一个整型数据就可以了。
但是要表示一个学生,需要很多数据,比如
struct Stu
{
string name; // 字符串类型变量表示姓名
int age; // 整型变量表示年龄
int Chinese, Math, English; // 整型变量表示分数
};
以及很多……
Stu Lass;
然后你就成功定义了一个叫做Stu数据类型的变量Lass
如果我要知道Lass的年龄,只需要用Lass.age访问就好了。
(当然女生的年龄拒绝访问)
然后我们还可以有结构体的成员函数
struct Stu
{
private:
void show_age();
}
就表示声明了一个让告诉你年龄的成员函数,具体实现方法未知,但是调用这个函数就会在屏幕上输出年龄。
然后你也看见了这个函数是私有的你不能访问
指针
晕又是一个难搞的概念
指针很抽象来说就是地址,一个指向地址的变量。
比如说我定义了一个整型变量x,那么计算机中储存这个变量的内存空间的位置就是我们的地址,通常用十六进制表示。
&也被称为取地址符,使用
cout << &x << endl;
可以知道x的地址
可以很形象理解为地址就是门牌号。
你住在这里,这里就是黄泉八路东六巷横四街1024号,就是你的地址。
如果计算机要找你,就是通过地址找到你,然后执行一波运算。
x=你
&x=黄泉八路东六巷横四街1024号
至于指针,就是存储你的地址的变量,
int *p=a;
p=&a;
表示定义了一个指向整型变量的指针p,定义的时候就顺便初始化为指向a,也就是存储的是a的地址。
如果定义之后也是可以改变的,就像下一行,直接赋值,p存储a的地址。
要访问指针指向的变量,其实就是通过地址访问变量,在指针前面加上*就可以
int a;
int *p=a;
cout << a << endl << &a << endl << p << endl << *p << endl;
自己验证一下啊怎么总看我胡说
然后就知道指针不止可以指向变量的嘛……
但是那些函数指针什么的我也没用过
所以你可以发现其实函数名就是一个指针,指向内存中存储着函数体的地址
void pass(){}
cout << pass << endl;
所以你就知道为什么sort最后传入参数cmp不需要括号了,因为他传入的是指针,指向那个函数。
然后其实数字名也是一个指针
int a[10];
cout << a << endl;
cout << &a[0] << endl;
他存储了数组首元素的地址,你访问a[i]就是访问*(&a[0]+i),从首元素地址向后访问i个变量。
结构体指针
通俗来说叫做链表……
struct Node
{
int dat;
Node *nxt;
}
你会发现,这个和数组差不多,只需要记住首元素是什么,然后就可以知道下一个元素的地址,访问下一个元素。又可以访问下下个元素……这样的方式就叫做链表。这个指针是指向一个结构体类型的。
我们知道成员运算符'.'有最强的左结合性,所以你
struct Node;
Node *p;
*p.nxt;
是会被理解成
*(p.nxt);
但是p是结构体指针啊,他没有成员变量nxt,这个时候编译器就会报错。
怎么办呢?用小括号啊
(*p).nxt;
当然还有一种方便的写法
p -> nxt;
文件
参考资料
文件的概念……没找到……自己理解一下……
基本操作:打开、关闭文本文件或二进制文件。
文件类型
分为文本文件和二进制文件
其实文本文件就是一种特殊的二进制文件。
我们都知道文件是用数据存储的。文本文件就是把数据按照一些规则(比如ASCII,uft-8之类的)把数据转化为可读文本。二进制文件就是你可以自定义规则。比如一些音频文件,其实本质都是二进制数据存储,但是由于解码的方式不同,所以解码出来就是一段好听的音乐。如果你强行用文本文件的规则去解码,出来就会得到一堆乱码。
参考文件
文件读写
白皮书上说有三种方法
- 打开文件
使用fopen函数
FILE *fopen( const char * filename, const char * mode );
表示打开路径为filename的文件,打开方式为mode
一下是mode的一些取值
模式 | 描述 |
---|---|
r | 打开一个已有的文本文件,允许读取文件。 |
w | 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该会被截断为零长度,重新写入。 |
a | 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。 |
r+ | 打开一个文本文件,允许读写文件。 |
w+ | 打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。 |
a+ | 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。 |
STL 模板们
sort
很简单吧……
template<typename _RandomAccessIterator, typename _Compare>
inline void
sort(_RandomAccessIterator __first, _RandomAccessIterator __last,
_Compare __comp)
把__first到__last里面的元素按升序排列,也就是前面比后面的“小”。
可以选择性提供函数__comp来自行定义“小”
比如你可以定义5比3小,即a>b叫做小,这样排序出来就是降序序列。
这里传入的函数是函数指针
// 如果a比b“小”就返回真
bool cmp(int a,int b) { return a>b; }
int a[8];
sort(a,a+8,cmp);