栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

【C++基础】学习笔记

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【C++基础】学习笔记

C++基础入门 1 C++统一格式初始化

初始化列表 解决方案

#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) 查看变量数据类型,使用时必须包含头文件

2 输入输出 C语言输入输出
#define  _CRT_SECURE_NO_WARNINGS
#include
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;
}
C++语言输入输出
#include
using 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’,也就是回车换行符
#include
using 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 = "<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函数

​ 当程序执行函数调用时,系统要建立栈空间,保护现场,传递参数以及控制程序执行的转移等等,这些工作需要系统时间和空间的开销。

请看如下程序段,读入一行字符串,逐个判断是否为数字字符:

#include
using 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
#include
using namespace std;
#include"A.h"
int main()
{
    intx=10,y=20;
    intz=0;
    z=my_add(x,y);
    cout< 

​ 如果函数的执行开销小于开栈清栈开销(函数体较小),使用inline处理效率高。如果函数的执行开销大于开栈清栈开销,使用普通函数方式处理。

内联函数与宏定义区别;
(1)内联函数在编译时展开,带参的宏在预编译时展开。
(2)内联函数直接嵌入到目标代码中,带参的宏是简单的做文本替换。
(3)内联函数有类型检测、语法判断等功能,宏只是替换。

6 缺省函数

​ 一般情况下,函数调用时的实参个数应与形参相同,但为了更方便地使用函数,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
#include
using 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.函数重载解析的步骤如下

  1. 确定函数调用考虑的重载函数的集合,确定函数调用中实参表的属性。
  2. 从重载函数集合中选择函数,该函数可以在(给出实参个数和类型)的情况下可以调用函数。
  3. 选择与调用最匹配的函数。
名字粉碎(名字修饰)

​ “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“代表一次重复;
4、参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;
5、参数表后以“@Z标识整个名字的结束,如果该函数无参数,则以“Z“标识结束。

C++函数是重载
//(?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)后加一个标识符构成,在这里两个关键字的意义相同,它们表示后面的参数名代表一个潜在的内置或用户设计的类型。

示例:

template
T 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)。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/883656.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号