CPP_Basic_Summary_0.7
1、C++要求函数的返回值不能是数组,但可以将数组作为结构或对象组成部分来返回
2、C++的编程风格因为函数原型必须存在,所以提倡main()置于最前面
3、函数原型不要求必须提供变量名,只需要类型列表就可以:
void test(double ar[],int);
void test(double [],int);
void test(double*,int);//三者同义
4、通常如果可能且仅当有意义时,原型自动将被传递的参数强制转换为期望的类型
5、在编译阶段进行的原型化被称为静态类型检查,这种检查可以捕获很多运行阶段难以捕获的错误
6、函数的内部变量(包括参数)是局部变量,函数结束将释放这些内存,按值传递时函数形参传递的是副本
7、在进行诸如阶乘这类大规模计算的时候,采用交替进行乘除运算的方式,可以有效防止中间结果超最大值而溢出
8、数组名在大部分时候等同于指向第一个元素的指针,但有以下情形除外:首先是数组声明用数组名来标记存储位置;其次,对数组名使用sizeof将得到整个数组的长度(字节为单位);然后是将地址运算符&用于数组名时,将返回整个数组的地址
9、将指针(包括数组名)+1,实际上是加上了一个与指针指向类型的长度(字节为单位)相等的值
10、函数调用数组必须通过数组名,也就是指针传递的方式,可以用数组名和数组容量搭配的方式:
void demo(double arr[],int size);
也能使用两个指针来指出数组的开头位置和结尾位置:
int sum_arr(cookies,cookies+size);
11、const double ar[]
:保护指针指向的内容不被修改;但只意味着对于ar来说不可以更改,别的指针或许可以,且指针ar本身也可以被修改
12、int* const pt=&age
:意味着指针本身不可被修改。简而言之:前置const锁数据,后置const锁指针,同时则都锁。需要非常注意的是,如果数组元素不是基本类型,而是指针或者指向指针的指针,则不能使用const(比如2D数组)
13、C++禁止将const的地址赋给非const指针,若数据类型本身不是指针,则可以将const数据或非const数据的地址赋给指向const的指针,但是只能将非const数据的地址赋给非const指针
14、2D数组的函数定义:int sum(int ar2[] [4],int size);
可见是单独传递行数的。ar2[r][c]==*(*(ar2+r)+c)
两者完全等价
15、const char* str="name";
即可声明指向字符串的指针str,此处const对于C++11标准是必须的
16、处理字符串中字符的标准方式:
while (*str)
{
statements;
str++;
}
此循环每次增加使得str指向下一个字符,直到最后的'\0'将使*str==0(空值字符的数字编码),从而中断循环
17、从后向前的循环一般可以节省额外的变量i,比如:
while (n-- >0)
{
pstr[n]=c;
}
18、子函数结束时,所有局部变量都会被释放,即返回的指针的内存会被释放,但是其值会被返回到main()中,故仍可以通过main()中的指针对新建的字符串相关内容进行访问
19、函数中使用结构可以使用实值直接传递和结构指针两种方式,但是出于效率考虑,应该优先使用指针。除此以外,还有引用的方式可以解决 表示和效率 两方面的问题
20、函数中使用string对象数组:
string list[size];
getline(cin,list[i]);
21、函数中使用array对象,除了基本数据类型,array还可以存储类对象:
array<double,4> expenses;
const array<string,4> sname {"Spring","Summer","Fall","Winter"};
void show(array<double,4> da);
void fill(array<double,4> *pa);
show(expenses);
fill(&expenses);
22、递归(也称 分而治之):C++允许递归调用除了main()以外的子函数:
void recurs(argumentlist)
{
statements1;
if (test)//test最终为false将跳出循环
recurs(arguments);//此处的递归调用应该更新test条件
statements2;
}//若statements1将按照调用顺序执行n次,则statements2将按照与函数调用相反的顺序执行n次
23、每一套递归调用都创建自己的一套变量,因此若程序达到第5次调用的时候,将有5个独立的变量:
void countdown(int n)
{
if (n>0)
countdown(n-1);
}
24、函数的地址是存储其机器语言代码的内存的开始地址。函数指针允许在不同的时间传递不同函数的地址,这意味着可以在不同的时间使用不同的函数。函数的地址就是函数名,需要注意的是区分函数本身和函数的返回值:
process(think);//传递的是函数的地址
thought(think());//传递的是函数的返回值
25、声明一个函数指针,通常可以先写出函数的原型,然后用诸如(*pf)这样的函数指针替换函数名即可:
double (*pf)(int);//pf是指向函数的指针,该函数返回值类型为double,形参为int
double* pf(int);//pf是一个函数,接受int形参,且返回值的类型为double*指针
26、需要注意C++特别规定,若pf是一个函数指针,则:
double y=(*pf)(5)
和double y=pf(5)
是完全等价的,也即此处(*pf)==pf
。为强调是函数指针,建议使用前者
27、类似地,以下代码含义完全一致:
cout<<(*p1)(av,3)<<*(*p1)(av,3)<<endl;
//第一部分是函数调用,返回double*指针,第二部分再次解除*,获得具体的double实值
cout<<p2(av,3)<<*p2(av,3)<<endl;
//同理,第一部分函数调用,返回double*指针,第二部分是具体的double实值
28、创建指向整个函数指针数组的指针:
const double* (*(*pd)[3]) (const double*,int)=&pa
//*pd表明pd是指针,右侧[3]说明指针指向一个数组
//*(*pd)最左侧的*表明数组的元素是指针
//观察左右两侧发现数组的元素是函数指针
//左侧const double*是返回值类型,右侧 (const double*,int)是形参
29、typedef用法:
例1:
typedef const double* (*p_fun) (const double*,int);
p_fun p1=f1;
p_fun pa[3] {f1,f2,f3};
p_fun (*pd) [3]=&pa;
例2:
void (*p1)(形参)=f1;
const char* (*p2)(形参)=f2;
void (*ap[5])(形参);
const char* (*(*pa)[10])(形参);
//可简化为:
typedef void (*p_f1)(形参);
p_f1 p1=f1;
typedef const char* (*p_f2)(形参);
p_f2 p2=f2;
p_f1 ap[5];
p_f2 (*pa)[10];
30、指针数组 数组指针 指针函数 函数指针
int *p[4]; //指针数组
//它是个有4个元素的数组, 每个元素的是指向整型的指针。(数组的每个元素都是指针)
int (*p)[4]; //数组指针。
//它是一个指针,指向有4个整型元素的数组。(一个指针指向有4个整型元素的数组)
int *func(void); //指针函数。
//无参函数, 返回整型指针。(函数的返回值为int*)
int (*func)(void);//函数指针
//可以指向无参, 且返回值为整型指针的函数。(函数的返回值为int)
31、复杂函数指针阅读说明:
右左法则其实并不是C++标准里面的内容,它是从C++标准的声明规定中归纳出来的方法。C++标准的声明规则,是用来解决如何创建声明的,而右左法则是用来解决如何辩识一个声明的,两者可以说是相反的。
右左法则:首先从最里面的圆括号(未定义的标识符)看起,然后往右看,再往左看。每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过程直到整个声明解析完毕。
对这个法则应该进行一个小小的修正,应该从未定义的标识符开始阅读,而不是从括号读起,之所以是未定义的标识符,是因为一个声明里面可能有多个标识符,但未定义的标识符只会有一个。
现在通过一些例子来讨论右左法则:
int (*func)(int *p);
首先找到那个未定义的标识符,就是func,它的外面有一对圆括号,而且左边是一个号,这说明func是一个指针,
然后跳出这个圆括号,先看右边,也是一个圆括号,这说明(func)是一个函数,而func是一个指向这类函数的指针,就是一个函数指针,这类函数具有int*类型的形参,返回值类型是 int。
int (*func)(int *p, int (*f)(int*));
func被一对括号包含,且左边有一个号,说明func是一个指针,跳出括号,右边也有个括号,那么func是一个指向函数的指针,这类函数具有int 和int ()(int)这样的形参,返回值为int类型。再来看一看func的形参int (f)(int),类似前面的解释,f也是一个函数指针,指向的函数具有int*类型的形参,返回值为int。
int (*func[5])(int *p);
func右边是一个[]运算符,说明func是一个具有5个元素的数组,func的左边有一个,说明func的元素是指针,要注意这里的不是修饰 func的,而是修饰func[5]的,原因是[]运算符优先级比高,func先跟[]结合,因此修饰的是func[5]。跳出这个括号,看右边,也是一对圆括号,说明func数组的元素是函数类型的指针,它所指向的函数具有int*类型的形参,返回值类型为int。
int (*(*func)[5])(int *p);
func被一个圆括号包含,左边又有一个,那么func是一个指针,跳出括号,右边是一个[]运算符号,说明func是一个指向数组的指针,现在往左看,左边有一个号,说明这个数组的元素是指针,再跳出括号,右边又有一个括号,说明这个数组的元素是指向函数的指针。总结一下,就是:func是一个指向数组的指针,这个数组的元素是函数指针,这些指针指向具有int*形参,返回值为int类型的函数。
int (*(*func)(int *p))[5];
func是一个函数指针,这类函数具有int*类型的形参,返回值是指向数组的指针,所指向的数组的元素是具有5个int元素的数组。
32、要注意有些复杂指针声明是非法的,例如:
int func(void) [5];
func是一个返回值为具有5个int元素的数组的函数。但C语言的函数返回值不能为数组,这是因为如果允许函数返回值为数组,
那么接收这个数组的内容的东西,也必须是一个数组,但C语言
的数组名是一个右值,它不能作为左值来接收另一个数组,因此函数返回值不能为数组。
int func[5](void);
func是一个具有5个元素的数组,这个数组的元素都是函数。这也是非法的,因为数组的元素除了类型必须一样外,每个元素所占用的内存空间也必须相同,显然函数是无法达到这个要求的,即使函数的类型一样,但函数所占用的空间通常是不相同的。实际当中,需要声明一个复杂指针时,如果把整个声明写成上面所示的形式,对程序可读性是一大损害。应该用typedef来对声明逐层分解,增强可读性。
33、用typedef的分解方法:
int (*(*func)[5][6])[7][8];
func是一个指向数组的指针,这类数组的元素是一个具有5X6个int元素的二维数组,而这个二维数组的元素又是一个二维数组。
typedef int (*PARA)[7][8];
typedef PARA (*func)[5][6];
int (*(*(*func)(int *))[5])(int *);
func是一个函数指针,这类函数的返回值是一个指向数组的指针,所指向数组的元素也是函数指针,指向的函数具有int*形参,返回值为int。
typedef int (*PARA1)(int*);
typedef PARA1 (*PARA2)[5];
typedef PARA2 (*func)(int*);
int (*(*func[7][8][9])(int*))[5];
func是一个数组,这个数组的元素是函数指针,这类函数具有int*的形参,返回值是指向数组的指针,所指向的数组的元素是具有5个int元素的数组。
typedef int (*PARA1)[5];
typedef PARA1 (*PARA2)(int*);
typedef PARA2 func[7][8][9];