C++ PrimerPlus 学习笔记(二)知识杂记与函数

知识杂记

表达式
  • 完整表达式是指该表达式不是另一个更大表达式的子表达式。

  • 对于y = (4 + x++) + (6 + x++);
    c++并不能保证每个子表达式计算完后x的值+1,只能保证整个表达式计算完后x+2。所以要避免这种情况。

  • >,< 的结合方向从左往右。

前缀格式++a与后缀格式a++

对于内置类型,两者执行速度一样。
对于类(用户设置的类型),前缀更快。

后缀比前缀优先级高,前缀优先级和解除引用运算符的优先级一样高。
(优先级一样高时从右往左)

运算符优先级

在这里插入图片描述
在这里插入图片描述

逗号运算符

优先级最低
从第一个表达式开始计算,再到第二个…
逗号表达式的值为最后一个表达式的值。

左值,右值
  • C语言中,左值可以位于赋值句的左侧,右值不能
  • 当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值时,用的是对象的身份(在内存中的位置)、
  • 之前见过的一些左右值:
    • 赋值运算符(=) 的左侧运算对象必须是非常量的左值,其结果也是左值
    • 取地址符(&) 的运算对象是左值,返回指向该运算对象的指针,该指针是右值
    • 内置解引用运算符,下标运算符,迭代器解引用运算符,string和vector的下标运算符 的结果都是左值
  • decltype 作用于表达式时,如果表达式结果为左值,decltype将得到一个引用类型;否则也返回右值
基于范围的 for 循环(c++11)
#include <iostream>
int main(int argc, char** argv) {
	using namespace std;
	int array[]={1,2,3,4,5,6,7,6,5,4,3,2,1};
	for(int i:array)//如果想修改数组,可把i设为引用for(int &i:array)
	{
		cout<<i<<endl;
	}
	return 0;
}

在这里插入图片描述

简单文件IO
#include<iostream>
#include<cstdlib>
#include<fstream>
#include<cstring>
int main(){
	using namespace std;
	ifstream readFile;
	ofstream writeFile;
	enum doWhat{readfile,writefile,exitProgram
	};
	int m;
	cout<<"Read the file input 0,write the file input 1,finish the program input 2\n";
	cin>>m;
	while(1){
	if(m==readfile){
		cout<<"readFile : Please input the name of file\n";
		char fileName[20];
		cin>>fileName;
		readFile.open(fileName);
		if(!readFile.is_open()){
			cout<<"Could not open the file"<<fileName<<endl;
			cout<<"Program terminating.\n";
			exit(EXIT_FAILURE);
		}
		char ch;
		/*string theText;
		readFile.getline>>theText;*/
		cout<<"The text of file is : "<<endl;
		while(readFile.get(ch)) 
			cout<<ch;
		if(readFile.eof()) cout<<"\nArrived the end of file\n";
		else if(readFile.fail()) {
		}
		else if(readFile.bad()) cout<<"\nFile is bad\n";
		readFile.close();
	}else if(m==writefile){
		cout<<"writeFile : Please input the name of file\n";
		char fileName[20];
		cin>>fileName;
		writeFile.open(fileName);
		cout<<"Please write the text you want input to file:\n"<<
				fileName<<":\n";
		char theText[100];
		cin.get();//空行处理 
		cin.clear();
		cin.get(theText,100);
		writeFile<<theText;	
		writeFile.close();		
	}else if(m==exitProgram) break;
	else cout<<"Please write 1 or 0 or 2 ! \n";
	cout<<"Read the file input 0,open the file input 1,finish the program input 2\n";
	cin>>m;
	}
	cout<<"OK Please write anything and Enter to exit ! ";
	cin.clear();
	cin.get();
	cin.get();
	return 0;
}

函数

  • 在c++中,函数参数括号为空则与使用void作为参数等效。
  • ANSI C 中函数参数括号为空表示不指出参数——即在后边定义参数列表。
    c++ 中表示不指出参数应使用 …(省略号)return_type function(...);
    不指出参数一般意味着要与可变参数的C函数(如printf())交互。
  • 当用指针作为函数参数时,可用const表示函数不可通过指针改变指针所指内存的值。
  • c++ 禁止将非const的指针赋给const的指针。
  • 在C++中,当且仅当用于函数头或函数原型中,int *arr和int arr[]的含义才是相同的。在其他的环境下,二者的含义并不同,前者代表指向int类型的指针,后者代表数组名。p213
区分 const int*pt 与 int* const pt;

const int*pt 表示指向常量。即不能通过该指针修改所指向内存的值。

int* const pt 表示常量指针。即不能修改该指针的值(不能再指向其他地方了)。

函数指针
函数名本身就是函数指针
声明函数指针

和声明函数一样,只是把函数名用(*pf)代替:

int (*pf)(double x,int y);//声明了一个参数(double x,int y),返回值类型为int 的函数指针。
int (*p1)(double x,int y) = f1;//初始化
auto p1 = f2;//auto 自动类型推断 下面细说
使用函数指针
y = (*pf)(3.14,6);
//c++还允许:
y = pf(3.14,6);
函数指针数组
const double* (*pa[3])(const double *,int)={f1,f2,f3};

运算符[]的优先级高于 * ,因此*pa[3] 表示pa是一个包含3个指针的数组。

不能用 auto 自动类型判断,因为 auto 只适用于单值初始化。

指向函数指针数组的指针呢:

方法一:
auto pc = &pa;
方法二:
const double *(*(*pd)[3])(const double *,int) = &pa;

注意:

*pp[3]  —— 包含三个指针元素的数组
(*pp)[3] —— 包含三个元素的数组,pp是指向该数组的指针
*(*pp)[3] —— 包含三个指针元素的数组,pp是指向该数组的指针

通过指向函数指针数组的指针进行函数调用:
(*pd)[i](p,6);
或: (*(*pd)[i])(p,6);

调用后返回指针所指的内存的值:
*(*pd)[i](p,6);
或:*(*(*pd)[i])(p,6);
typedef 进行简化
int (*pf)(double x,int y);//声明了一个参数(double x,int y),返回值类型为int 的函数指针。

typedef int (*p_func)(double x,int y);//声明了一个参数(double x,int y),返回值类型为int 的函数指针类型的别名:p_func
p_func pa[3] = {f1,f2,f3};
自动类型推断 auto

确保变量的类型与赋给它的初值的类型一致。即使你赋的初值类型不对。(见笔记一,基础与数据)

内联函数 inline

省去函数调用的处理时间,因而速度较快。
一般用于代码较短,本身处理就很快的函数。

inline int square(int x);
...
inlint int square (int x)
{
	return x*x;
}

或直接:
inlint int square (int x){return x*x;}

注意:

  • 仍然是按值传递参数(相比于 宏 的优点)
  • 不可递归(不能在函数内部调用自己)
引用变量
int i = 12;
int & n =i;

引用变量 n 将是变量i 的别名,其值与地址都是一样的。

注意:

  • 引用变量必须在声明时就初始化。
  • 引用变量一旦被初始化,就一直“效忠”于该变量,不可更改。
  • 不能引用表达式,除非是const引用(可初始化为一般表达式)
  • 目前所说是左值引用,C+11中新增“右值引用”
主要用法——用于函数参数

对于既能用引用传递也能按值传递的函数,若参数较大(内存较大的对象或结构)时,用 const 引用参数更快些,否则就按值传递。

若引用参数是 const 时,在以下两种情况下会创建临时变量。

  • 实参的类型正确,但不是左值
  • 实参类型不正确,但能转换为正确类型

左值:是可被引用的数据对象,现在 变量和const变量都可视为左值。
非左值:包括字面常量,多项式等
一旦创建临时变量就和按值传递很像了,不会影响实参,是为了防止函数改变实参(因为引用参数是const的,本意就是为了防止改变实参)

指导原则p274
  • 对于使用传递的值而不作修改的函数
    • 如果数据对象很小,如内置数据类型或小型结构,则按值传递;
    • 如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针;
    • 如果数据对象是较大的结构,则使用const指针或const引用,以提高程序的效率。这样可以节省复制结构所需的时间和空间;
    • 如果数据对象是类对象,则使用const引用。传递类对象参数的标准方式是按引用传递。
  • 对于修改调用函数中的数据的函数
    • 如果数据对象是内置数据类型,则使用指针(引用也行);
    • 如果数据对象是数组,则只能使用指针;
    • 如果数据对象是结构,则使用引用或指针;
    • 如果数据对象是类,则使用引用
const 引用
  • 不可通过引用修改被引用对象的值,无论被引用对象是不是const 常量

  • const 引用可绑定到非常量对象,字面值常量,一般表达式,或者可自动转换的其他类型变量

    double v =3.14;
    int i=2;
    const int &r0 = 3;
    const int &r1 = v;
    const int &r2 = i+1;
    //创建值为3的临时int量让 r1,r2 绑定
    //所以只能是const引用
    
建议:尽可能使用 const

理由有三:

  • 避免无意中修改数据的编程错误
  • 可接受const与非const实参
  • 可正确生成并使用临时变量

——尽可能将引用形参声明为const

返回引用
改程序只是演示了其本质,正确用法看第二个程序
#include"iostream"
using namespace std;
int & is0 (int& num); 
int main()
{
	int i=12;
	cout<<"i : "<<i<<endl;
	is0(i)++;
	cout<<"i : "<<i<<endl;
	getchar();
	return 0;
 } 
 int & is0(int& num)
 {
 	if(num==0)
 	 cout<<num<<"is 0 \n";
 	else 
 		cout<<num<<"isn't 0\n";
 		
 	return num;
 }

结果:

i : 12
12isn't 0
i : 13

返回的仍是返回值所引用的变量。是左值(被引用的变量不是函数的临时变量)。
常规类型的返回是右值,因为它在临时内存中。

#include"iostream"
using namespace std;
const int & is0 (const int& num); 
int main(int argc,char ** argv)
{
	int i=12,n;
	cout<<"i : "<<i<<endl;
	n=is0(i);
	cout<<"n : "<<i<<endl;
	return 0;
 } 
const int & is0 (const int& num)
 {
 	if(num==0)
 	 cout<<num<<"is 0 \n";
 	else 
 		cout<<num<<"isn't 0\n";
 	
 	return num;
 }

结果:

i : 12
12isn't 0
n : 12

优点:一般按值传递的返回,其实 是创建了临时的变量,而直接返回引用省略了这一步骤,更高效。
注意:

  • 避免返回函数终止时不再存在的内存单元的引用。
    方法一:返回一个作为参数传递给函数的引用
    方法二:在函数中new一块内存,并返回指针,注意不要忘了delete该内存。
返回const引用——不能被修改的左值
编程习惯——避免模糊,比如使用const更加准确清晰。
另一种引用——右值引用&&

可引用右值

一般用于库设计人员能够提供有些操作的更有效实现。

默认参数
  • 必须字声明函数原型时指出
  • 只能从右往左指出默认参数
  • 填实参时只能从左往右,不能跳过
  • 函数定义则与一般定义一样,不用管默认值(也管不了)
int func(int x,int y=0);
函数重载

关键——函数特征标(参数列表)——参数类型,参数个数
这些不同就可以重载

  • 调用时若没有与实参类型完全匹配的就会尝试使用 标准类型转换强制进行匹配。
  • 但是若是转换强制匹配时发现有多个转换方式,就会出错。
  • 将类型引用于类型本身视为同一个特征标。
  • 不能只有返回类型不同而特征标相同。返回类型不同的同时特征标必须不同。
  • 重载时优先调用最匹配的版本
函数模板
template <typename T>
void MySwap(T x,T y);
...

template <typename T>
void MySwap(T x,T y)
{
	T t;
	t=x;
	x=y;
	y=t;
}
  • 关键字 template 和 typename(可替换为class)
  • 将同一算法用于不同类型
  • T 代表任一类型,编译器将会在具体情况时将其实例化
  • 一般将模板放在头文件里
  • 模板并不能缩短程序,最终还是要实例化的
  • 可以有多个模板参数
    emplate <typename T1,typename T2>
    ADD(T1 x,T2 y);
重载模板
template<typename T>
void Swap(T &a, T &b);

template<typename T>
void Swap(T *a,T *b,int n);//void Swap(T a[],T b[],int n);
  • 函数模板定义时可包含具体的类型
显式具体化

将模板函数的某些类型具体化,再定义。优先于一般模板。

第三代具体化:

  • 对于给定的函数名,可以有非模板函数,模板函数和显式具体化模板函数以及他们的重载函数
  • 非模板版本优先于显式具体化个模板版本,而显式具体化优先于模板生成的版本
  • 显式具体化的原型及定义应以template<>打头
struct job
{
char name[40];
double salary;
int floor;
}
...
template<> void Swap<job>(job&,job&);
//Simple Form:
template<> void Swap(job&,job&);
显式实例化
template void Swap<int>(int,int);

或者在程序中使用函数来创建显式实例化
在需要类型自动转换时可以指定转换成哪种函数

//或者在语句中使用:
template <class T>
T Add(T a,T b)
{
	return a+b;
}
...
int m=6;
double x = 12.2;
cout<<Add<double>(x,m)<<emdl;
注意——引用类型不能相互转换

int& 不能转换成double &或double

下面是错误的

int m = 5;
double x = 14.3;
↓↓↓↓↓ Error ↓↓↓↓↓
Swap<double>(m,x);
↑↑↑↑↑ Error ↑↑↑↑↑
编译器选择使用哪个函数的版本

对于单个参数,从最佳到最差的顺序如下:

  • 完全匹配,但常规函数优于模板
  • 提升转换(char→int,int→double)
  • 标准转换(int→char,long→double)
  • 用户定义的转换,如类声明中定义的转换。


具体规则:P289 ~ P294

模板函数中的判断类型

C++11 新关键字—— decltype

int x;
decltype(x) y;// y 和 x有相同的类型

可用于函数模板中自动判断返回类型

template<class T1,class T2>
void ft(T1 x,T2 y)
{
	...
	decltype(x+y) xpy = x +y;
	...
}

对于decltype(expression) var的规则:

  • 若expression是未被括号括起的标识符,则var 与 expression 的类型一致
  • 若 expression 是一个函数调用,则var是函数的返回类型(并不会真的调用函数)
  • 若 expression 是括号括起来的标识符则是其类型的引用
  • 否则 ,var 与 expression的类型一致
返回类型的判断(后置返回类型)(c++11)
auto ADD(T1 x,T2 y) -> decltype((x+y));
...
template <typename T1,typename T2>
auto ADD(T1 x,T2 y) -> decltype((x+y))
{
	return (x+y);
}

内存模型

  • 静态变量初始化
    可以用表达式,函数返回确定静态变量的初始值
  • C++11新增 constexpr 关键字用于创建常量表达式
  • 静态外部变量将隐藏常规外部变量P114
  • 静态局部变量只进行一次初始化
  • 单定义规则:外部全局变量只能定义一次,初始化时可以extern(可选),不初始化则默认为0,但可以多次声明(extern)
存储说明符和cv-限定符
存储说明符
  • auto(c++11中不是,c++11中只用于自动类型推断,以前用于显式表示自动存储的变量)
  • register(以前表示存储器变量,c++11中用于显式表示自动存储的变量)
  • static
  • extern
  • thread_local(c++11新增)
  • multable

thread_local可与 static 和 extern 结合使用,指出变量的持续性与其所属线程的持续性相同(就像静态变量与程序的关系)。

multable 指出:即使结构(或类)变量为const,其某个成员也可以被修改

cv-限定符
  • const
  • volatile

volatile :表明,即使程序代码没有对内存单元进行修改,其值也可能发生改变P317(一编译器会优化一些变量的处理,即使硬件上内存单元发生了改变,有时也不会被程序及时察觉,使用该关键词可避免该情况)

const:对于const声明的常量,其链接性为内部的。这意味着可以在头文件中声明这种常量而不用担心重复定义。(c++中)

  • 若希望某const常量的链接性为外部的,可以用 extern 覆盖其内部链接性。这时也需要在其他使用常量的文件中用 extern,但是此时该const常量只能初始化一次(就像一般的外部变量)

    //file1.cpp
    extern const int bufSize = fcn();
    //file1.h
    extern const int bufSize;
    
  • 其作用域就像一般变量一样,对于同名const常量,局部可以隐藏外部

函数的链接性

一般默认为外部

  • 可在声明原型和定义处用关键字static 表明是内部,这意味着函数只能在该文件内使用。其他文件也可以定义同名该函数,static的函数将会屏蔽外部同名函数。
  • 非内联函数也符合单定义规则,链接性为外部的函数(一般函数)只能有一个定义。
  • 内联函数不收单定义规则影响,意味着可将内联函数定义放在头文件,C++要求同一个内联函数的所有定义都必须相同。
语言链接性

c语言与C++的名称修饰不同,需要对C的预编译进行处理,详情见P319。

new 运算符
new 运算符的初始化

单值初始化

int *p = new int(6);
int *pi = new int{6};

多值(结构,数组)初始化(c++11 列表初始化)

int *p = new int[4]{1,2,3,4};

struct where{double x,double y,double z};
...
where *pw = new where{1.2,3.6,4.2}; 
定位new

需包含头文件 new

#include<new>
...
char * buffer1[40];
...
double *p = new (buffer1)double (12.88);
...
delete p;

名称空间

  • 名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中 => 所以,默认名称空间中声明的变量链接性为外部的(除非它引用了常量)
  using namespace std; //using编译指令
  using std::cout;//using声明指令

尽量使用 using声明 而不用 using 编译指令,有重名时用using声明会报错,提示二义性,而using 编译指令将覆盖。

#include<iostream>
using namespace std;
namespace Jill
{
	int iApple=2;
	double dPencil;
 } 
int main()
{
	//using Jill::iApple;//声明名称空间会与同名变量冲突,错误;
	using namespace std;//using编译指令,不会与同名变量冲突,而是被局部的同名变量覆盖,但仍可用作用域解析符。
	int iApple;
	iApple=1;
	cout<<Jill::iApple<<endl;//用作用域解析符使用名称空间中的变量
	cout<<iApple; 
	return 0; 
 } 

结果:

2
1
  • 名称空间可嵌套
namespace elements
{
	namespace fire
	{
		int flame;
		...
	}
	float water;
}
...
使用 fire内部名称空间:
using namespace elements::fire;

  • 可在名称空间中使用 using 编译指令和 using 声明
namespace myth
{
	using Jill::iApple;
	using namespace elements;
	using std::cout;
	using std::cin;
}
...
访问 iApple:
cout<<myth::iApple;
或
cout<<Jill::iApple;
  • 名称空间是可传递的。上例中 using namespace myth; 相当于 using namespace myth; + using namespace Jill;
  • 可以给名称空间创建别名:
namespace MT = myth;
//用于简化:
namespace FE = elements::fire;
using FE::flame; 
  • 未命名的名称空间
namespace
{
	int count;
}

提供了链接性为内部的静态变量的替代品
放在文件中效果为 static int count;

名称空间使用指导
  • 使用在已命名的名称空间中声明的变量,而不是使用外部全局变量
  • 使用在已命名的名称空间中声明的变量,而不是使用静态全局变量
  • 不要在头文件中使用 using 编译指令
  • 导入名称首选 using声明 或 作用域解析符
  • 对于 using 声明首
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值