初始化列表 解决方案
#include#include using namespace std; int main() { int a = 10; //等价 int b(10); int c{ 10 }; int ar[10] = { 1,2,3,4,5,6,7,8,9,10 }; int br[10]{ 1,2,3,4,5,6,7,8,9,10 }; cout << "a: " << typeid(a).name() << endl; //int cout << "b: " << typeid(b).name() << endl; //int cout << "c: " << typeid(c).name() << endl; //int cout << "ar: " << typeid(ar).name() << endl; //int [10] cout << "dr: " << typeid(br).name() << endl; //int [10] return 0; }
typeid(var).(name) 查看变量数据类型,使用时必须包含头文件
#define _CRT_SECURE_NO_WARNINGS #includeC++语言输入输出int main() { int a = 0; char ch = ' '; scanf("%d %c", &a, &ch); //2 n printf("a = %d ch = %cn", a, ch); //a = 2 ch = n return 0; }
#includeusing namespace std; int main() { const int n = 128; char str[n]; cin>>str; // 输入 yhp hello this cout< > 提取符 // << 插入符 // endl => 'n'; 换行符 // 错误使用方式: cin>>a,ch; cout< 使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含**< iostream >头文件以及std标准命名空间**。endl 相当于 ‘n’;
当 cin 读取数据时,它会传递并忽略任何前导白色空格字符(空格、制表符或换行符)。一旦它接触到第一个非空格字符即开始阅读,当它读取到下一个空白字符时,它将停止读取。可以输入 “Mark” 或 “Twain”,但不能输入 “Mark Twain”,因为 cin 不能输入包含嵌入空格的字符串。
输入字符串istream& getline(istream &is, string &str, char delim);
- istream &is :表示一个输入流,譬如cin;
- string &str:表示把从输入流读入的字符串存放在这个字符串中
- char delim:表示遇到这个字符停止读入,在不设置的情况下系统默认该字符为’n’,也就是回车换行符
C++的 getline 函数。此函数可读取整行,包括前导和嵌入的空格,并将其存储在字符串对象中。
// This program illustrates using the getline function //to read character data into a string object. #include#include // Header file needed to use string objects using namespace std; int main() { string name; string city; cout << "Please enter your name: "; getline(cin, name); cout << "Enter the city you live in: "; getline(cin, city); cout << "Hello, " << name << endl; cout << "You live in " << city << endl; return 0; } cin.getline(char* s, streamsize n, char delim);
- char* s:表示cin中读取的字符串里首字符的位置
- streamsize n:表示要读取的字符个数
- char delim:表示遇到这个字符停止读入,在不设置的情况下系统默认该字符为’n’,也就是回车换行符
#includeusing namespace std; int main() { const int n = 128; char str[n]; cin >> str; // 输入 aaa hello this cout << str << endl; // aaa cin.getline(str, n); // aaa hello this cout << str << endl; // aaa hello this cin.getline(str, n, '#'); // aaa this # go to cout << str << endl; //aaa this; return 0; } C++的getline 的函数。此函数可读取整行,包括前导和嵌入的空格,并将其存储在字符串对象中。
3 const与指针 const在C和C++中的区别int main() { const int n = 10; // C语言中以变量为主。 int ar[n] ={1,2}; // error; int *p =(int*) &n; // *p = 100; printf("%n = %d *p = %d n",n,*p); return 0; } int main() { const int n = 10; // C++ 语言中以常量为主。 int ar[n] = {1,2,3,4}; // ok; int *p = (int *)&n; *p = 100; cout<<"n = "<const与指针的关系 int main() { int a = 10, b = 10; int* p1 = &a; // 普通指针 const int* p2 = &a; // 指向为常性(解引用为常性) int const* p2 = &a; p2 = &b; //可以修改自身的值 //*p2 = 2; error 指向不可修改 int* const p3 = &a; // 指针变量自身为常性 //p3 = &b; //error 不可以修改自身的值 *p3 = 2; //可以修改指向 const int* const p4 = &a; // 指向(解引用)和指针变量自身都为常性 //p4 = &b; error 不可以修改自身的值 //*pa = 2; error 不可以修改指向 }常变量与指针int main() { const int a = 10; int *p1 = &a; // error; const int *p2 = &a; // ok; int * const p3 = &a; // error; const int * const *p4 = &a; // ok; int *p5 = (int*) &a; // ok 不安全 return 0; }同类型指针的赋值兼容规程int main() { int a = 10,b = 20; int *p = &a; int *s1 = p; // ok; const int *s2 = p; // ok; int * const s3 = p; // ok; const int * const s4 = p; //ok; }总结: 能力强的指针赋值给能力收缩的指针
4 引用(别名)类型& 引用变量名称=变量名称;
这就是引用变量的定义。&和类型结合称之为引用符号,不是取地址的符,代表别名的意思。
引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
引用的特点定义引用必须初始化
没有空引用
没有引用的引用
const int&是万能引用
int main() { int a=10; int &x;//error定义引用必须初始化 int &y=NULL;//error 没有空引用 int &b=a; int &&c=b;//error 没有引用的引用、所谓的二级引用 }const引用int main() { int a=10; const int b=20; int&x=b;//error; const int&x=b;//ok; const int&y=a;//ok; const int&z=10;//ok; return 0; } //为了安全 const int &x=b; //int tmp=b; //const int&x=b; const int&z=10; //int tmp=10; //const int&z=tmp;引用作为形参替代指针
示例:使用指针交换两个整型值
void my_swap(int *ap,int *bp) //使用指针交换两个整型值 { assert(ap!=NULL && bp!=NULL); int tmp=*ap; *ap=*bp; *bp=tmp; } void my_Swap(int& a int& b) //使用引用交换两个整型值 { int tmp = a; a=b; b=tmp; }其他引用形式int main() { int a=10,b=20; int ar[5]={1,2,3,4,5}; int* p=&a; int *&rp=p;//引用指针 int &x=ar[0]; int (&br)[5]=ar;//引用数组,br是ar数组的别名 return 0; }引用和指针的区别从语法规则上讲,指针变量存储某个实例(变量或对象)的地址;引用是某个实例的别名
程序为指针变量分配内存区域;而不为引用分配内存区域。
解引用是指针使用时要在前加“*”;引用可以直接使用。
引用在定义时就被初始化,之后无法改变(不能是其他实例的引用)。
指针变量的值可以为空(NULL,nullptr);没有空引用。
指针变量作为形参时需要测试它的合法性(判空NULL),引用不需要判空;
对指针变量使用“sizeof”得到的是指针变量的大小。对引用变量使用“sizeof”得到的是变量的大小。
理论上指针的级数没有限制;但引用只有一级。即不存在引用的引用,但可以有指针的指针。
++引用与++指针的效果不一样
#include#include #include using namespace std; int main() { int ar[5] = { 1,3,5,7,9 }; int* ip = ar; int& b = ar[0]; ++b; //2,ar[0]+1 ++ip; //3,ar[0]-->ar[1] cout << b << endl; cout << *ip << endl; } 对引用的操作直接反应到所引用的实体(变量或对象)。
不可以对函数中的局部变量或对象以引用或指针方式返回。
int* func_1() { int a=10; return &a; } int& func_2() { int a=10; return a; } //都是错误的引用的实质//原码 int main() { int a=10; int* ip=&a; int& b=a; *ip=100; b=200; return 0; }从汇编层次理解引用与指针变量的区别,在C++中引用的本质就是指针常量
5 inline函数
当程序执行函数调用时,系统要建立栈空间,保护现场,传递参数以及控制程序执行的转移等等,这些工作需要系统时间和空间的开销。
请看如下程序段,读入一行字符串,逐个判断是否为数字字符:
#includeusing namespace std; bool IsNumber(char ch) { return ch >= '0'&&ch<='9'?1:0; } int main() { char ch; while (cin.get(ch), ch != 'n') { if (IsNumber(ch)) { cout << "是数字字符" << endl; cin.get(ch); } else { cout << "不是数字字符" << endl; cin.get(ch); } } return 0; } 当函数功能简单,使用频率很高,为了提高效率,直接将函数的代码嵌入到程序中。但这个办法有缺点,一是相同代码重复书写,二是程序可读性往往没有使用函数的好。
为了协调好效率和可读性之间的矛盾,C++提供了另一种方法,即定义内联函数,方法是在定义函数时用修饰词inline。
inline bool IsNumber(char ch) { return ch >= '0'&&ch<='9'?1:0; } 加inline关键字将其改成内联函数,在编译期间编译器能够在调用点内联展开该函数。
实例:
在debug模式下,设置编译器。
要点:
inline是一种以空间换时间的做法,省去调用函数额开销。但当函数体的代码过长或者是递归函数即便加上inline关键字,也不会在调用点以内联展开该函数。 inline对于编译器而言只是一个建议,编译器会自动优化。
inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会
找不到。//A.h #pragma once #include"A.h" int my_add(int a,int b); //inline int my_add(int a,int b); //A.cpp inline int my_add(int a,int b) { return a+b; } //Mainrest.cpp #includeusing namespace std; #include"A.h" int main() { intx=10,y=20; intz=0; z=my_add(x,y); cout< 如果函数的执行开销小于开栈清栈开销(函数体较小),使用inline处理效率高。如果函数的执行开销大于开栈清栈开销,使用普通函数方式处理。
内联函数与宏定义区别;
6 缺省函数
(1)内联函数在编译时展开,带参的宏在预编译时展开。
(2)内联函数直接嵌入到目标代码中,带参的宏是简单的做文本替换。
(3)内联函数有类型检测、语法判断等功能,宏只是替换。 一般情况下,函数调用时的实参个数应与形参相同,但为了更方便地使用函数,C++也允许定义具有缺省参数的函数,这种函数调用时,实参个数可以与形参不相同。
缺省参数指在定义函数时为形参指定缺省值(默认值)。
这样的函数在调用时,对于缺省参数,可以给出实参值,也可以不给出参数值。如果给出实参,将实参传递给形参进行调用,如果不给出实参,则按缺省值进行调用。
缺省参数的函数调用:缺省实参并不一定是常量表达式,可以是任意表达式,甚至可以通过函数调用给出。如果缺省实参是任意表达式,则函数每次被调用时该表达式被重新求值。但表达式必须有意义;
#define _CRT_SECURE_NO_WARNINGS 1 #include#include #include #include using namespace std; void func(int a, int b, int c = int{}, int d = int(), int e = 0) { printf("a = %d,b = %d,c = %d,d = %d,e = %d", a, b, c, d, e); } int main() { func(1, 2, 3, 4, 5); //a = 1, b = 2, c = 3, d = 4, e = 5 func(1,2); //a = 1,b = 2,c = 0,d = 0,e = 0 return 0; } 习惯上,缺省参数在公共头文件包含的函数声明中指定,不要函数的定义中指定。
如果在函数的定义中指定缺省参数值,在公共头文件包含的函数声明中不能再次指定缺省参数值。
缺省实参不一定必须是常量表达式可以使用任意表达式。
using namespace std; int my_rand() { srand(time(NULL)); int ra = rand() % 100; //0-99 return ra; } void func(int a, int b=my_rand()) { cout << a << " " << b << endl; } int main() { func(1); return 0; } 缺省参数可以有多个,但所有缺省参数必须放在参数表的右侧,即先定义所有的非缺省参数,再定义缺省参数。这是因为在函数调用时,参数自左向右逐个匹配,当实参和形参个数不一致时只有这样才不会产生二义性。
void fun(int a, int b = 23 , int c = 8000) { cout << "a = " << a << " b = " << b << " c = " << c << endl; } int main() { fun(12); fun(10,20); fun(10,20,30); fun(10,,30); // error; 不能隔着赋值 return 0; } 在多文件结构中,把默认值放在函数声明中。
//A.h #ifndef AH #define A_H void fun(int a,int b=23,int c=8000); //也可以是下列形式 void fun(int,int=23,int = 8000);//ok; #endif //A.cpp #includeusing namespace std; #include"A.h" //void fun(int a,int b=10,int c=20);//error; //定义中不再给出缺省值 void fun(int a,int b,int c) { cout <<"a="< using namespace std; #include"A.h" int main() { fun(12); //12 23 8000 fun(10,20); //10 20 8000 fun(10,20,30); //10 20 30 return 0; } 缺省值为开辟在堆区的地址
void func(int n, int* p = (int*)malloc(sizeof(int))) {}; int main() { int a = 10; func(10, &a); func(10); return 0; }缺省值为引用
void func(const int& a = 10)//int tmp =10;const int &a=tmp; { } int main() { const int& x = 10;//error; func(); int y=20; func(y); //引用变量 const int c=30; func(c); //引用常量 func(100); //引用字面常量 }7 函数重载C语言实现int,double,char 类型的比较大小函数。不允许函数重名
int my_max_i(int a,int b){ reurn a>b?a:b;} double my_max_d(double a,double b){ return a>b?a:b;} char my_max_c(char a,char b){ return a>b?a:b;} 这些函数都执行了相同的一般性动作;都返回两个形参中的最大值;从用户的角度来看,只有一种操作,就是判断最大值,至于怎样完成其细节,函数的用户一点也不关心。
这种词汇上的复杂性不是“判断参数中的最大值“问题本身固有的,而是反映了程序设计环境的一种局限性:在同一个域中出现的名字必须指向一个唯实体(函数体)。
这种复杂性给程序员带来了一个实际问题,他们必须记住或查找每一个函数名字。函数重载把程序员从这种词汇复杂性中解放出来。
函数重载的概念:
在C++中可以为两个或两个以上的函数提供相同的函数名称,只要参数类型不同,或参数类型相同而参数的个数不同,称为函数重载。
//my_max + 参数表,每个函数的函数名一样,但参数表不一样,是唯一的 //在编译链接过程中,确认调用关系 int my_max(int a, int b) { return a > b ? a : b; } char my_max(char a, char b) { return a > b ? a : b; } double my_max(double a, double b) { return a > b ? a : b; } int main() { int ix = my_max(12, 23); double dx = my_max(12.23,34.56); char chx = my_max('a','b'); return 0; }编译器的工作:
当一个函数名在同一个域中被声明多次时,编译器按如下步骤解释第二个(以及后续的)的声明。
如果两个函数的参数表中参数的个数或类型或顺序不同,则认为这两个函数是重载。
例如://重载函数 void print(int a,char b); void print(char a,int b);函数重载的规则1.如果两个函数的参数表相同,但是返回类型不同,会被标记为编译错误:函数的重复声明。
int my_max(int a, int b) { return a > b ? a : b; } unsigned int my_max(int a, int b)//error E0311无法重裁仅按返回类型区分的函数 { return a > b ? a : b; } int main() { int ix = my_max(12, 23); unsigned int dx = my_max(12, 23);//error; return 0; }2.参数表的比较过程与形参名无关。
//声明同一个函数 int my_add(int a, int b) { return a + b; } int my_add(int x, int y) // C2084 int my_add(int, int)”已有主体 { return x+ y; } int main() { int ix = my_add(12, 23); //二义性,不知道调用哪个函数 return 0; }3.如果在两个函数的参数表中,只有缺省实参不同,则第二个声明被视为第一个的重复声明。
void Print(int *br,int n); void Print(int *br,int 1en=10); //重复定义4.typedef只是为现有的数据类型提供了一个替换名,它并没有创建一个新类型,因此,如果两个函数参数表的区别只在于一个使用了typedef,而另一个使用了与typedef相应的类型。则该参数表被视为相同的参数列表。
typedef unsigned int u_int; int Print(u_int a); int Print(unsigned int b);5.当一个形参类型有const或volatile修饰时,如果形参是按值传递方式定义,在识别函数声明是否相同时,并不考虑const和volatile修饰符。
void fun(int a) {} void fun(const int a) {} //忽略const,C2084 函数“void fun(int)”已有主体6.当一个形参类型有const或 volatile 修饰时如果形参定义指针或引用时,在识别函数声明是否相同时,就要考虑const和 volatile修饰符。
void fun(int *p){} void fun(const int *p)} //const修饰指向,指向无法修改,和void fun(int *p)不同,属于函数重载 void fun(int &a){} void fun(const int &a){} //实质为const int* const a,指向和自身的值都无法修改,和void fun(int &a)仅是自身的值无法修改,属于重载7.注意函数调用的二义性;如果在两个函数的参数表中,形参类型相同,而形参个数不同,形参默认值将会影响函数的重载。
void fun(int a){} void fun(int a, int b = 10) {} //参数存在默认值 int main() { //E0308 有多个 重载函数 "fun" 实例与参数列表匹配: fun(10,20); //调用函数时依据参数个数区分 fun(10); //有缺省值,几个调用第一个fun(),也可调用第二个fun(),有二义性 }8.函数重载解析的步骤如下
名字粉碎(名字修饰)
- 确定函数调用考虑的重载函数的集合,确定函数调用中实参表的属性。
- 从重载函数集合中选择函数,该函数可以在(给出实参个数和类型)的情况下可以调用函数。
- 选择与调用最匹配的函数。
“C或者“C++”函数在内部(编译和链接)通过修饰名识别。修饰名是编译器在编译函数定义或者原型时生成的字符串。
修饰名由函数名、类名、调用约定、返回类型、参数等共同决定。
调用约定:
__stdcall 是Pascal程序的缺省调用方式,通常用于Win32Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。
C调用约定(即用__cdecl关键字说明)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数的函数只能使用该调用约定)。
__fastcall 调用约定是“人”如其名,它的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈),在函数名修饰约定方面,它和前两者均不同。
thiscall仅仅应用于”C++”类的成员函数。this 指针存放于ECX寄存器,参数从右到左压。
thiscall 不是关键词,因此不能被程序员指定。 在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预编译(预处理)、编译、汇编、链接。Name Mangling是一种在编译过程中,将函数名、变量名的名字重新命名的机制。
C语言编译时函数名修饰约定规则
C语言的名字修饰规则非常简单,_cdecl是C/C++的缺省调用方式,调用约定函数名字前面添加了下划线前缀。
格式:_functionname
__stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个"@"符号和其参数的字节数。
格式:__functionname@number;
__fastcall 调用约定在输出函数名前加上一个“@“符号,函数名后面也是一个”@“符号和其参数的字节数。格式为:@functionname@number
C++编译时函数名修饰约定规则
__cdecl调用约定:
1、以“?”标识函数名的开始,后跟函数名;
2、函数名后面以“@@YA”标识参数表的开始,后跟参数表;
3、参数表以代号表示:
X–void,D–char,
E–unsigned char,
F–short,
H–int,
I-unsigned int,J-long,
K-unsigned long,
M–float,
N-double,
N–bool,
…PA–表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以“0"代替,一个“0“代表一次重复;
C++函数是重载
4、参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;
5、参数表后以“@Z标识整个名字的结束,如果该函数无参数,则以“Z“标识结束。//(?my_max@@YAHHH@Z) int my max(int a, int b); //(?my_max@@YADDD@z) char my_max(char a, char b); //(?my_max@@YANNN@Z) double my-max(double a, double b); int main() { my_max(12,23); my_max('a','b'); my_max(12.23,34.45); return 0; }关键字:
extern“C”:函数名以C的方式修饰约定规则;
extern"C++":函数名以C++的方式修饰约定规则;参考资料:S++的函数重载C/C++函数遇用约定
8 函数模板 为了代码重用,代码就必须是通用的;通用的代码就必须不受数据类型的限制。那么我们可以把数据类型改为一个设计参数。这种类型的程序设计称为参数化(parameterize)程序设计。软件模块由模板(template)构造。包括函数模板(function template)和类模板(class template)。
函数模板可以用来创建一个通用功能的函数,以支持多种不同形参,简化重载函数的设计。
函数模板定义如下:
template<模板参数表> 返回类型 函数名(形式参数表) { ...;//函数体 } <模板参数表>(template parameter list)尖括号中不能为空,参数可以有多个,用逗号分开。模板参数主要是模板类型参数。
模板类型参数(template type parameter)代表一种类型,由关键字class 或typename(建议用typename)后加一个标识符构成,在这里两个关键字的意义相同,它们表示后面的参数名代表一个潜在的内置或用户设计的类型。
示例:
templateT my_max(T a,T b) { return a>b?a:b; } int main() { my_max(12,23); my-max('a','b'); my_max(12.23,34.45); return 0; } 函数模板根据一组实际类型或(和)值构造出独立的函数的过程通常是隐式发生的,称为模板实参推演(template argument deduction)。
在编译过程中,根据函数模板的实参构造出独立的函数,称为模板函数(template function)。这个构造过程被称为模板实例化(template instantiation)。



