ue4c++【基础知识】

1.函数指针

 编译时,系统会为每个函数分配一段存储空间,这段存储空间的首地址称为这个函数的地址,函数名表示这个地址。我们可以用指针变量来存放这个地址,这个指针变量就叫作函数指针变量,简称函数指针

所以函数指针的定义方式为:

函数返回值类型 (* 指针变量名) (函数参数列表);
int(*p)(int, int);

定义了一个指针变量 p,该指针变量可以指向返回值类型为 int 型,且有两个整型参数的函数。p 的类型为 int(*)(int,int)。

int Func(int x);   /*声明一个函数*/
int (*p) (int x);  /*定义一个函数指针*/
p = Func;          /*将Func函数的首地址赋给指针变量p*/
# include <stdio.h>
int Max(int, int);  //函数声明
int main(void)
{
    int(*p)(int, int);  //定义一个函数指针
    int a, b, c;
    p = Max;  //把函数Max赋给指针变量p, 使p指向Max函数
    printf("please enter a and b:");
    scanf("%d%d", &a, &b);
    c = (*p)(a, b);  //通过函数指针调用Max函数
    printf("a = %d\nb = %d\nmax = %d\n", a, b, c);
    return 0;
}
int Max(int x, int y)  //定义Max函数
{
    int z;
    if (x > y)
    {
        z = x;
    }
    else
    {
        z = y;
    }
    return z;
}

最后需要注意的是,指向函数的指针变量没有 ++ 和 -- 运算。

2.引用

编译器不会为引用开辟新的内存空间,它不是一个变量,而是已存在变量的一个别名。它与被引用的变量共用同一块内存空间。

#include <iostream>
#include <Windows.h>
using namespace std;
//定义左值引用并初始化为变量a,以16进制打印a、temp的地址
int main()
{
	int a = 23;
	int& temp = a;
	cout << "a:" << a << '\t' << "temp:" << temp << endl;
	cout << "&a:" << std::hex << &a << '\t' << "&temp:" << &temp << endl;
	system("pause");
	return 0;
}

     引用的特点:

  1. 定义引用时必须给初始化
  2. 没有空引用
  3. 没有所谓的二级引用
  4. 一个变量可以有多个引用(就相当于一个变量有好几个别名,这是可以的)

在这里插入图片描述

关于常量引用:

常引用实际上是一种万能引用 

(1)引用普通变量,但是不能修改变量

在这里插入图片描述

 (2)引用常量

在这里插入图片描述

 (3)引用字面常量

//常量引用的格式:const int &a。
const int & a = 10;
//引用字面常量时,分两步走,首先定义一个临时量 去引用临时量 不是引用真实的字面常量10。

(4)引用其他类型的变量

double dval = 3.14;
const int& a= dval;
//实际情况为编译器生成了一个临时变量b,常量引用绑定的是临时变量b。
//const int b = dval;
//const int&a =b;

总结

扫描二维码关注公众号,回复: 14571428 查看本文章
int main(){

  double dval = 3.14159;

  const int &ir = 1024;                       //仅对const引用才是合法的

  const int &ir2 = dval;                      //仅对const引用才是合法的

  const double &dr = dval + 1.0;        //仅对const引用才是合法的

}

关于对数组的引用:

在引用数组时,必须知道数组的大小

int main()
{
    int a = 10;
    int b = 10;
    int ar[5] = { 1,2,3,4,5 };
    int& x = ar[0];  //ok
    int(&x)[5] = ar; //ok   没有[5]无法编译通过
  
    return 0;
}

关于对指针的引用:

int main()
{
	int a = 100;
	int *p = &a;
	int * &rp = p;
 
	cout << a << endl;
	cout << *p << endl;
	cout << *rp << endl; //这里为什么要将*放在前面,因为p的类型是 int * 作为一个整体哦!!
 
	cout << p << endl;
	cout << rp << endl;
 
	getchar();
	return 0;
}
/*
100
100
100
012FF84C
012FF84C
*/

引用作为返回值:

当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边。

#include <iostream>
 
using namespace std;
 
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
 
double& setValues(int i) {  
   double& ref = vals[i];    
   return ref;   // 返回第 i 个元素的引用,ref 是一个引用变量,ref 引用 vals[i]
 
 
}
 
// 要调用上面定义函数的主函数
int main ()
{
 
   cout << "改变前的值" << endl;
   for ( int i = 0; i < 5; i++ )
   {
       cout << "vals[" << i << "] = ";
       cout << vals[i] << endl;
   }
 
   setValues(1) = 20.23; // 改变第 2 个元素
   setValues(3) = 70.8;  // 改变第 4 个元素
 
   cout << "改变后的值" << endl;
   for ( int i = 0; i < 5; i++ )
   {
       cout << "vals[" << i << "] = ";
       cout << vals[i] << endl;
   }
   return 0;
}
改变前的值
vals[0] = 10.1
vals[1] = 12.6
vals[2] = 33.1
vals[3] = 24.1
vals[4] = 50
改变后的值
vals[0] = 10.1
vals[1] = 20.23
vals[2] = 33.1
vals[3] = 70.8
vals[4] = 50

3.模板

泛型编程是编写与类型无关的逻辑代码,是实现代码复用的一种手段,其中模板是泛型编程的基础

模板:模板包含函数模板和类模板

(1)函数模板:代表了一个函数家族,该函数与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本 。

        格式:

template<typename T1,typename T2,...>
返回值类型  函数名(参数列表)
{
  ...
}
//typename是定义模板函数的关键字,在这里也可以使用class,
//不可使用struct,为了便于区分,建议尽量使用typename,
//使用class容易与C++中的类混淆。

       例子:

#include <iostream>
using namespace std;
template<class T>
void Swap(T & x, T & y)
{
    T tmp = x;
    x = y;
    y = tmp;
}
int main()
{
    int n = 2, m = 8;
    Swap(n, m);  //编译器自动生成 void Swap (int &, int &)函数
    cout<<"n="<<n<<" "<<"m="<<m<<endl; 
	double f = 2.8, g = 8.2;
    Swap(f, g);  //编译器自动生成 void Swap (double &, double &)函数
    cout<<"f="<<f<<" "<<"g="<<g<<endl;
    char *a = "abc",*b = "def"; //编译器自动生成 void Swap (char &, char &)函数
    Swap(a,b);
    cout<<"a="<<a<<" "<<"b="<<b<<endl;
	return 0;
}

 模板函数的特殊化:

尽管模板函数能够对多种类型值进行操作,但是不同类型值在有些操作时需要先进行一定处理即特化,比如字符串是无法直接进行大小比较的,但我们可以比较其字符串长短,下面我们以Compare函数为例。

#include <iostream>
#include <cstring>
using namespace std;
//函数模板
template<class T>
bool Compare(T t1,T t2){
    return t1==t2;
}
 
template<> //函数模板特化
bool Compare(char *t1,char *t2){
    return strcmp(t1,t2)==0;
}
 
int main(int argc, char* argv[])
{
    int a = 2;
    int b = 2;
    int c = 8;
    double d = 2.8;
    double e = 2.8;
    double f = 8.2;
	char str1[] = "abc";
    char str2[] = "abc";
    char str3[] = "def";
    cout<<Compare(a,b)<<endl;
    cout<<Compare(a,c)<<endl;
    cout<<Compare(d,e)<<endl;
    cout<<Compare(e,f)<<endl;
    cout<<Compare(str1,str2)<<endl;
	cout<<Compare(str1,str3)<<endl;
    return 0;
}
#include<iostream>
#include<algorithm>
#include<ctime> 
using namespace std;
/*
1.自动生成一个随机数组
2.对其进行选择排序
3.使用模板函数进行不同类型数据的排序
如:整型,浮点型,字符型,结构体类型(自定义) 
*/ 
//定义模板函数 
template <typename T>
void SelectSort(T a[],int n){
	for(int i=0;i<n;i++){
		int min_index=i;
		for(int j=i+1;j<n;j++)
			if(a[j]<a[min_index])
			  min_index=j;
		swap(a[i],a[min_index]);	
	}
}
 
template <typename P>
void PrintArray(P a[],int n){
	for(int i=0;i<n;i++){
		cout<<a[i]<<" ";
	}
	cout<<endl;
}
//随机数组 
int* RandomArray(int n,int rangeL,int rangeR){
	int *arr=new int[n];//创建一个大小为n的数组 
	srand(time(NULL));//以时间为"种子"产生随机数 
	for(int i=0;i<n;i++){
		arr[i]=rand()%(rangeR-rangeL+1)+rangeL;//生成指定区间[rangeL,rangeR]里的数 
	}
	return arr;
}
 
int main(){
	float b[5]={0.5,2.7,1.5,15.8,10.2};
	char c[5]={'e','a','c','d','b'};
	int n;
	cout<<"请输入数据规模n:";cin>>n;
	int* a=RandomArray(n,1,100);//生成[1,100]内的随机数组成的数组 
	cout<<"整型排序:";SelectSort(a,n); PrintArray(a,n);//整型
	cout<<"浮点型排序:";SelectSort(b,5); PrintArray(b,5);//浮点型
	cout<<"字符型排序:";SelectSort(c,5); PrintArray(c,5);//字符型 
	return 0;
} 

(2)模板类:如果一个类中的数据成员的数据类型不能确定,或者是某个成员函数的参数或返回值的类型不能确定,就可以将此类声明为模板,它的存在不是代表一个具体的、实际的类

例子:

template <typename T>
class A
{
public:
	A(T t){
		this->t = t;
	}
 
	T& getT(){
		return t;
	}
 
private:
	T t;
};
 
#include <iostream>
using namespace std;
 
template <typename T>
class A
{
public:
	//函数的参数列表使用虚拟类型
	A(T t = 0){
		this->t = t;
	}
	//成员函数返回值使用虚拟类型
	T& getT(){
		return t;
	}
 
private:
	//成员变量使用虚拟类型
	T t;
};
 
void printA(A<int>& a) {
	cout << a.getT() << endl;
}
 
int main(void) {
	//1.模板类定义类对象,必须显示指定类型
	//2.模板种如果使用了构造函数,则遵守以前的类的构造函数的调用规则
	A<int>  a(666);
	cout << a.getT() << endl;
 
	//模板类做为函数参数
	printA(a);
	system("pause");
	return 0;
}

模板类的全特化和偏特化

全特化:对所有模板类型进行特化

偏特化:对模板类型做一些限制特化

#include <iostream>//全特化
using namespace std;
 
template<typename T1, typename T2>
class A{
        public:
                void function(T1 value1, T2 value2){
                        cout<<"value1 = "<<value1<<endl;
                        cout<<"value2 = "<<value2<<endl;
                }
};
 
template<>
class A<int, double>{ // 类型明确化,为全特化类
        public:
                void function(int value1, double value2){
                        cout<<"intValue = "<<value1<<endl;
                        cout<<"doubleValue = "<<value2<<endl;
                }
};
 
int main(){
        A<int, double> a;
        a.function(28, 28.8);
        return 0;
}                   
#include <iostream>//偏特化
using namespace std;
 
template<typename T1, typename T2>
class A{
        public:
                void function(T1 value1, T2 value2){
                        cout<<"value1 = "<<value1<<endl;
                        cout<<"value2 = "<<value2<<endl;
                }
};
 
template<typename T>
class A<T, double>{ // 部分类型明确化,为偏特化类
        public:
                void function(T value1, double value2){
                        cout<<"charValue = "<<value1<<endl;
                        cout<<"doubleValue = "<<value2<<endl;
                }
};
 
int main(){
        A<char, double> a;
        a.function('a', 28.8);
        return 0;
}

1.父类一般类,子类是模板类, 和普通继承的玩法类似

2.子类是一般类,父类是模板类,继承时必须在子类里实例化父类的类型参数

3.父类和子类都时模板类时,子类的虚拟的类型可以传递到父类中

class A {
public:
	A(int temp = 0) {
		this->temp = temp;
	}
	~A(){}
private:
	int temp;
};
 
template <typename T>
class B :public A{
public:
	B(T t = 0) :A(666) {
		this->t = t;
	}
	~B(){}
private:
	T t;
};
class A {
public:
	A(int temp = 0) {
		this->temp = temp;
	}
	~A(){}
private:
	int temp;
};
 
template <typename T>
class B :public A{
public:
	B(T t = 0) :A(666) {
		this->t = t;
	}
	~B(){}
private:
	T t;
};
template <typename T>
class A {
public:
	A(T t = 0) {
		this->t = t;
	}
	~A(){}
private:
	T t;
};
 
 
class B:public A<int> {
public:
    //也可以不显示指定,直接A(666)
	B(int temp = 0):A<int>(666) {
		this->temp = temp;
	}
	~B() {}
private:
	int temp;
};

4宏

首先说一下预处理指令:

以#开头的行,都是预处理指令,用于指示编译器做一些预处理工作。例如#include  "xxx.h"

预处理指令不是语句,结尾不用加分号

其实宏就是在源码在编译前一个预处理指令,将代码中指定宏命令找到并以文本的形式进行替换而已。

 1)使用宏定义常量    示例:#define MAXSIZE 200

#include<stdio.h>
#define M 20
int main()
{
    int a = M;
    int b = 20;
    /*
    编译后变成以下代码
    int a = 20;
    int b = 20;
    */
    printf("%d %d",a,b);
    return 0;
}

这里写图片描述

   2)使用宏定义表达式  示例:define MAX(a,b) ((a)<(b)?(b):(a))

 

额外加一个例子

#define LOG(x) std::cout << x << std::endl

int main(int argc, char const *argv[])
{
    LOG("hello");
    std::cin.get();
}

3) 使用宏来调用多个语句  示例播放mp3文件,并打印语句

 

4)使用宏定义函数     

#include <iostream>
#include <string>

#define MAIN            \
    int main()          \
    {                   \
        std::cin.get(); \
    }
MAIN

 还有一个重要的点就是宏的条件编译

1)如果没有定义debug,就定义一下

2)如果定义了MY_MAX,我们就定义MY_MAX_EX

 3)  如果定义了PR_DEBUG就将LOG(x) 定义为 std::cout << x << std::endl进行输出。否则定义为空不进行任何输出。

#include <iostream>
#include <string>

#ifdef PR_DEBUG
#define LOG(x) std::cout << x << std::endl
#else
#define LOG(x)
#endif

int main(int argc, char const *argv[])
{

    LOG("hello");
    std::cin.get();
}

4)  #if的后面接的是表达式

#if (MAX10)||(MAX20)
code…
#endif

它的作用是:如果(MAX10)||(MAX20)成立,那么编译器就会把其中的#if 与 #endif之间的代码编译进去(注意:是编译进去,不是执行!!) 

5虚函数

那些被virtual关键字修饰的成员函数,就是虚函数

其作用就是实现多态性  

例子:

#include<iostream>
using namespace std;
class A
{
    public:
        void print()
        {
            cout<<"This is A"<<endl;
        }
};
 
class B : public A
{
    public:
        void print()
        {
            cout<<"This is B"<<endl;
        }
};
 
int main()
{
    //为了在以后便于区分,我这段main()代码叫做main1
    A a;
    B b;
    a.print();
    b.print();
    return 0;
}

输出:分别是“This is A”、“This is B”。

此时我们修改main函数

int main()
{
    //main2
    A a;
    B b;
    A *p1 = &a;
    A *p2 = &b;
    p1->print();
    p2->print();
    return 0;
}

输出:两个“This is A”

问题来了,p2明明指向的是class B的对象但却是调用的class A的print()函数,这不是我们所期望的结果,那么解决这个问题就需要用到虚函数。

class A
{
    public:
        virtual void print(){cout<<"This is A"<<endl;}
};
class B : public A
{
    public:
    void print(){cout<<"This is B"<<endl;}
};

毫无疑问,class A的成员函数print()已经成了虚函数,那么class B的print()成了虚函数了吗?回答是Yes,我们只需在把基类的成员函数设为virtual,其派生类的相应的函数也会自动变为虚函数。所以,class B的print()也成了虚函数。那么对于在派生类的相应函数前是否需要用virtual关键字修饰,那就是你自己的问题了(语法上可加可不加,不加的话编译器会自动加上,但为了阅读方便和规范性,建议加上)。

修改后的输出结果:This is A和This is B

猜你喜欢

转载自blog.csdn.net/zhang2362167998/article/details/126362470